diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c1d928d --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +voices/*.* +models/*.* +outputs/*.* +finetune/*.* +system/config/at_github_sha.json +system/tts_engines/tts_engines.json +confignew.json +diagnostics.log diff --git a/system/SillyTavern Extension/For AllTalk V2/Instructions.txt b/system/SillyTavern Extension/For AllTalk V2/Instructions.txt new file mode 100644 index 0000000..4673f61 --- /dev/null +++ b/system/SillyTavern Extension/For AllTalk V2/Instructions.txt @@ -0,0 +1 @@ +Place the `alltalk.js` in your `\SillyTavern\public\scripts\extensions\tts\` folder, replacing the existing file. \ No newline at end of file diff --git a/system/SillyTavern Extension/For AllTalk V2/alltalk.js b/system/SillyTavern Extension/For AllTalk V2/alltalk.js new file mode 100644 index 0000000..ac8669b --- /dev/null +++ b/system/SillyTavern Extension/For AllTalk V2/alltalk.js @@ -0,0 +1,978 @@ +import { doExtrasFetch, getApiUrl, modules } from '../../extensions.js'; +import { saveTtsProviderSettings } from './index.js'; + +export { AllTalkTtsProvider }; + +class AllTalkTtsProvider { + //########// + // Config // + //########// + + settings = {}; + constructor() { + // Initialize with default settings if they are not already set + this.settings = { + provider_endpoint: this.settings.provider_endpoint || 'http://localhost:7851', + language: this.settings.language || 'en', + voiceMap: this.settings.voiceMap || {}, + at_generation_method: this.settings.at_generation_method || 'standard_generation', + narrator_enabled: this.settings.narrator_enabled || 'false', + at_narrator_text_not_inside: this.settings.at_narrator_text_not_inside || 'narrator', + narrator_voice_gen: this.settings.narrator_voice_gen || 'Please set a voice', + rvc_character_voice: this.settings.rvc_character_voice || 'Disabled', + rvc_narrator_voice: this.settings.rvc_narrator_voice || 'Disabled', + finetuned_model: this.settings.finetuned_model || 'false' + }; + // Separate property for dynamically updated settings from the server + this.dynamicSettings = { + modelsAvailable: [], + currentModel: '', + deepspeed_available: false, + deepspeed_enabled: false, + lowvram_capable: false, + lowvram_enabled: false, + }; + this.rvcVoices = []; // Initialize rvcVoices as an empty array + } + ready = false; + voices = []; + separator = '. '; + audioElement = document.createElement('audio'); + + languageLabels = { + 'Arabic': 'ar', + 'Brazilian Portuguese': 'pt', + 'Chinese': 'zh-cn', + 'Czech': 'cs', + 'Dutch': 'nl', + 'English': 'en', + 'French': 'fr', + 'German': 'de', + 'Italian': 'it', + 'Polish': 'pl', + 'Russian': 'ru', + 'Spanish': 'es', + 'Turkish': 'tr', + 'Japanese': 'ja', + 'Korean': 'ko', + 'Hungarian': 'hu', + 'Hindi': 'hi', + }; + + get settingsHtml() { + let html = `
AllTalk Settings
`; + + html += `
+ +
+ + +
+
`; + + html += `
+ +
+ + +
+ +
+ + +
+ +
`; + + html += `
+
+ + +
+
+ + +
+
`; + + html += `
+
+ + +
+
+ + +
+
`; + + + html += `
+
+ + +
+ +
+ + +
+
`; + + + html += `
+
+ + +
+
+ + +
+
+ Status: Ready +
+
+ +
+
`; + + + html += `
+
+ AllTalk Config & Docs. +
+ +
+ AllTalk Website. +
+
`; + + 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. +
+
`; + + return html; + } + + + //#################// + // Startup ST & AT // + //#################// + + async loadSettings(settings) { + updateStatus('Offline'); + + if (Object.keys(settings).length === 0) { + console.info('Using default AllTalk TTS Provider settings'); + } else { + // Populate settings with provided values, ignoring server-provided settings + for (const key in settings) { + if (key in this.settings) { + this.settings[key] = settings[key]; + } else { + console.debug(`Ignoring non-user-configurable setting: ${key}`); + } + } + } + + // Update UI elements to reflect the loaded settings + $('#at_server').val(this.settings.provider_endpoint); + $('#language_options').val(this.settings.language); + $('#at_generation_method').val(this.settings.at_generation_method); + $('#at_narrator_enabled').val(this.settings.narrator_enabled); + $('#at_narrator_text_not_inside').val(this.settings.at_narrator_text_not_inside); + $('#narrator_voice').val(this.settings.narrator_voice_gen); + $('#rvc_character_voice').val(this.settings.rvc_character_voice); + $('#rvc_narrator_voice').val(this.settings.rvc_narrator_voice); + + console.debug('AllTalkTTS: Settings loaded'); + try { + // Check if TTS provider is ready + await this.checkReady(); + await this.updateSettingsFromServer(); // Fetch dynamic settings from the TTS server + await this.fetchTtsVoiceObjects(); // Fetch voices only if service is ready + await this.fetchRvcVoiceObjects(); // Fetch RVC voices + this.updateNarratorVoicesDropdown(); + this.updateLanguageDropdown(); + this.setupEventListeners(); + this.applySettingsToHTML(); + updateStatus('Ready'); + } catch (error) { + console.error("Error loading settings:", error); + updateStatus('Offline'); + } + } + + + applySettingsToHTML() { + const narratorVoiceSelect = document.getElementById('narrator_voice'); + const atNarratorSelect = document.getElementById('at_narrator_enabled'); + const textNotInsideSelect = document.getElementById('at_narrator_text_not_inside'); + const generationMethodSelect = document.getElementById('at_generation_method'); + this.settings.narrator_voice = this.settings.narrator_voice_gen; + // Apply settings to Narrator Voice dropdown + if (narratorVoiceSelect && this.settings.narrator_voice) { + narratorVoiceSelect.value = this.settings.narrator_voice; // Remove the parentheses + } + // Apply settings to AT Narrator Enabled dropdown + if (atNarratorSelect) { + const ttsPassAsterisksCheckbox = document.getElementById('tts_pass_asterisks'); + const ttsNarrateQuotedCheckbox = document.getElementById('tts_narrate_quoted'); + const ttsNarrateDialoguesCheckbox = document.getElementById('tts_narrate_dialogues'); + if (this.settings.narrator_enabled) { + ttsPassAsterisksCheckbox.checked = false; + $('#tts_pass_asterisks').click(); + $('#tts_pass_asterisks').trigger('change'); + } + if (!this.settings.narrator_enabled) { + ttsPassAsterisksCheckbox.checked = true; + $('#tts_pass_asterisks').click(); + $('#tts_pass_asterisks').trigger('change'); + } + if (this.settings.narrator_enabled) { + ttsNarrateQuotedCheckbox.checked = true; + ttsNarrateDialoguesCheckbox.checked = true; + $('#tts_narrate_quoted').click(); + $('#tts_narrate_quoted').trigger('change'); + $('#tts_narrate_dialogues').click(); + $('#tts_narrate_dialogues').trigger('change'); + } + atNarratorSelect.value = this.settings.narrator_enabled.toString(); + this.settings.narrator_enabled = this.settings.narrator_enabled.toString(); + } + const languageSelect = document.getElementById('language_options'); + if (languageSelect && this.settings.language) { + languageSelect.value = this.settings.language; + } + if (textNotInsideSelect && this.settings.text_not_inside) { + textNotInsideSelect.value = this.settings.text_not_inside; + this.settings.at_narrator_text_not_inside = this.settings.text_not_inside; + } + if (generationMethodSelect && this.settings.at_generation_method) { + generationMethodSelect.value = this.settings.at_generation_method; + } + const isStreamingEnabled = this.settings.at_generation_method === 'streaming_enabled'; + if (isStreamingEnabled) { + if (atNarratorSelect) atNarratorSelect.disabled = true; + if (textNotInsideSelect) textNotInsideSelect.disabled = true; + if (narratorVoiceSelect) narratorVoiceSelect.disabled = true; + } else { + if (atNarratorSelect) atNarratorSelect.disabled = false; + if (textNotInsideSelect) textNotInsideSelect.disabled = !this.settings.narrator_enabled; + if (narratorVoiceSelect) narratorVoiceSelect.disabled = !this.settings.narrator_enabled; + } + } + + + //##############################// + // Check AT Server is Available // + //##############################// + + async checkReady() { + try { + const response = await fetch(`${this.settings.provider_endpoint}/api/ready`); + // Check if the HTTP request was successful + if (!response.ok) { + throw new Error(`HTTP Error Response: ${response.status} ${response.statusText}`); + } + const statusText = await response.text(); + // Check if the response is 'Ready' + if (statusText === 'Ready') { + this.ready = true; // Set the ready flag to true + console.log('TTS service is ready.'); + } else { + this.ready = false; + console.log('TTS service is not ready.'); + } + } catch (error) { + console.error('Error checking TTS service readiness:', error); + this.ready = false; // Ensure ready flag is set to false in case of error + } + } + + //######################// + // Get Available Voices // + //######################// + + async fetchTtsVoiceObjects() { + const response = await fetch(`${this.settings.provider_endpoint}/api/voices`); + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`HTTP ${response.status}: ${errorText}`); + } + const data = await response.json(); + const voices = data.voices.map(filename => { + return { + name: filename, + voice_id: filename, + preview_url: null, // Preview URL will be dynamically generated + lang: 'en' // Default language + }; + }); + this.voices = voices; // Assign to the class property + return voices; // Also return this list + } + + async fetchRvcVoiceObjects() { + console.log('Fetching RVC Voices'); + try { + const response = await fetch(`${this.settings.provider_endpoint}/api/rvcvoices`); + if (!response.ok) { + const errorText = await response.text(); + 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 + return voices; // Also return this list + } catch (error) { + console.error('Error fetching RVC voices:', error); + throw error; + } + } + + + + //##########################################// + // Get Current AT Server Config & Update ST // + //##########################################// + + async updateSettingsFromServer() { + try { + const response = await fetch(`${this.settings.provider_endpoint}/api/currentsettings`); + if (!response.ok) { + throw new Error(`Failed to fetch current settings: ${response.statusText}`); + } + const currentSettings = await response.json(); + currentSettings.models_available.sort((a, b) => a.name.localeCompare(b.name)); + + this.settings.enginesAvailable = currentSettings.engines_available; + this.settings.currentEngineLoaded = currentSettings.current_engine_loaded; + this.settings.modelsAvailable = currentSettings.models_available; + this.settings.currentModel = currentSettings.current_model_loaded; + this.settings.deepspeed_capable = currentSettings.deepspeed_capable; + this.settings.deepspeed_available = currentSettings.deepspeed_available; + this.settings.deepspeed_enabled = currentSettings.deepspeed_enabled; + this.settings.lowvram_capable = currentSettings.lowvram_capable; + this.settings.lowvram_enabled = currentSettings.lowvram_enabled; + + await this.fetchRvcVoiceObjects(); // Fetch RVC voices + + this.updateModelDropdown(); + this.updateCheckboxes(); + this.updateRvcVoiceDropdowns(); // Update the RVC voice dropdowns + } catch (error) { + console.error(`Error updating settings from server: ${error}`); + } + } + + updateRvcVoiceDropdowns() { + const rvcCharacterVoiceSelect = document.getElementById('rvc_character_voice'); + const rvcNarratorVoiceSelect = document.getElementById('rvc_narrator_voice'); + if (rvcCharacterVoiceSelect && this.rvcVoices) { + rvcCharacterVoiceSelect.innerHTML = ''; + for (let voice of this.rvcVoices) { + const option = document.createElement('option'); + option.value = voice.voice_id; + option.textContent = voice.name; + rvcCharacterVoiceSelect.appendChild(option); + } + } + if (rvcNarratorVoiceSelect && this.rvcVoices) { + rvcNarratorVoiceSelect.innerHTML = ''; + for (let voice of this.rvcVoices) { + const option = document.createElement('option'); + option.value = voice.voice_id; + option.textContent = voice.name; + rvcNarratorVoiceSelect.appendChild(option); + } + } + } + + //###################################################// + // Get Current AT Server Config & Update ST (Models) // + //###################################################// + + updateModelDropdown() { + const modelSelect = document.getElementById('switch_model'); + if (modelSelect) { + modelSelect.innerHTML = ''; // Clear existing options + this.settings.modelsAvailable.forEach(model => { + const option = document.createElement('option'); + option.value = model.name; + option.textContent = model.name; // Use model name directly + option.selected = model.name === this.settings.currentModel; + modelSelect.appendChild(option); + }); + } + } + + //#######################################################// + // Get Current AT Server Config & Update ST (DS and LVR) // + //#######################################################// + + updateCheckboxes() { + const deepspeedCheckbox = document.getElementById('deepspeed'); + const lowVramCheckbox = document.getElementById('low_vram'); + + // Handle DeepSpeed checkbox + if (deepspeedCheckbox) { + if (this.settings.deepspeed_capable) { + // If TTS engine is capable of using DeepSpeed + deepspeedCheckbox.disabled = !this.settings.deepspeed_available; + this.settings.deepspeed_enabled = this.settings.deepspeed_available && this.settings.deepspeed_enabled; + } else { + // If TTS engine is NOT capable of using DeepSpeed + deepspeedCheckbox.disabled = true; + this.settings.deepspeed_enabled = false; + } + deepspeedCheckbox.checked = this.settings.deepspeed_enabled; + } + + // Handle Low VRAM checkbox + if (lowVramCheckbox) { + if (this.settings.lowvram_capable) { + // If TTS engine is capable of low VRAM + lowVramCheckbox.disabled = false; + } else { + // If TTS engine is NOT capable of low VRAM + lowVramCheckbox.disabled = true; + this.settings.lowvram_enabled = false; + } + lowVramCheckbox.checked = this.settings.lowvram_enabled; + } + } + + + //###############################################################// + // Get Current AT Server Config & Update ST (AT Narrator Voices) // + //###############################################################// + + updateNarratorVoicesDropdown() { + const narratorVoiceSelect = document.getElementById('narrator_voice'); + if (narratorVoiceSelect && this.voices) { + // Clear existing options + narratorVoiceSelect.innerHTML = ''; + // Add new options + for (let voice of this.voices) { + const option = document.createElement('option'); + option.value = voice.voice_id; + option.textContent = voice.name; + narratorVoiceSelect.appendChild(option); + } + } + } + + //######################################################// + // Get Current AT Server Config & Update ST (Languages) // + //######################################################// + + updateLanguageDropdown() { + const languageSelect = document.getElementById('language_options'); + if (languageSelect) { + // Ensure default language is set + this.settings.language = this.settings.language; + + languageSelect.innerHTML = ''; + for (let language in this.languageLabels) { + const option = document.createElement('option'); + option.value = this.languageLabels[language]; + option.textContent = language; + if (this.languageLabels[language] === this.settings.language) { + option.selected = true; + } + languageSelect.appendChild(option); + } + } + } + + //########################################// + // Start AT TTS extenstion page listeners // + //########################################// + + 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 + const selectedModel = event.target.value; + console.log(`Selected model: ${selectedModel}`); // Debugging statement + // Set status to Processing + updateStatus('Processing'); + try { + const response = await fetch(`${this.settings.provider_endpoint}/api/reload?tts_method=${encodeURIComponent(selectedModel)}`, { + 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 + updateStatus('Ready'); + } catch (error) { + console.error("POST request error:", error); // Debugging statement + // Set status to Error in case of failure + updateStatus('Error'); + } + + // Handle response or error + }; + + + 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 + }); + } + + const rvcNarratorVoiceSelect = document.getElementById('rvc_narrator_voice'); + if (rvcNarratorVoiceSelect) { + rvcNarratorVoiceSelect.addEventListener('change', (event) => { + this.settings.rvcnarrator_voice_gen = event.target.value; + this.onSettingsChange(); // Save the settings after change + }); + } + + 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 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' + }); + 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 + updateStatus('Ready'); + } catch (error) { + console.error("POST request error:", error); // Debugging statement + // Set status to Error in case of failure + updateStatus('Error'); + } + }); + } + + function lowvramdebounce(func, delay) { + let debounceTimer; + return function (...args) { + const context = this; + clearTimeout(debounceTimer); + debounceTimer = setTimeout(() => func.apply(context, args), delay); + }; + } + + // Low VRAM Listener + const lowVramCheckbox = document.getElementById('low_vram'); + 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' + }); + 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 + updateStatus('Ready'); + } catch (error) { + console.error("POST request error:", error); // Debugging statement + // Set status to Error in case of failure + updateStatus('Error'); + } + }; + + const debouncedHandleLowVramChange = lowvramdebounce(handleLowVramChange, 300); // Adjust delay as needed + + lowVramCheckbox.addEventListener('change', debouncedHandleLowVramChange); + } + + + // Narrator Voice Dropdown Listener + 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 + }); + } + + const textNotInsideSelect = document.getElementById('at_narrator_text_not_inside'); + if (textNotInsideSelect) { + textNotInsideSelect.addEventListener('change', (event) => { + this.settings.text_not_inside = event.target.value; + this.onSettingsChange(); // Save the settings after change + }); + } + + // AT Narrator Dropdown Listener + const atNarratorSelect = document.getElementById('at_narrator_enabled'); + const ttsPassAsterisksCheckbox = document.getElementById('tts_pass_asterisks'); + const ttsNarrateQuotedCheckbox = document.getElementById('tts_narrate_quoted'); + const ttsNarrateDialoguesCheckbox = document.getElementById('tts_narrate_dialogues'); + + if (atNarratorSelect && textNotInsideSelect && narratorVoiceSelect) { + atNarratorSelect.addEventListener('change', (event) => { + 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(); + $('#tts_pass_asterisks').trigger('change'); + ttsNarrateQuotedCheckbox.checked = true; + ttsNarrateDialoguesCheckbox.checked = true; + $('#tts_narrate_quoted').click(); + $('#tts_narrate_quoted').trigger('change'); + $('#tts_narrate_dialogues').click(); + $('#tts_narrate_dialogues').trigger('change'); + } else if (narratorOption === 'silent') { + ttsPassAsterisksCheckbox.checked = false; + $('#tts_pass_asterisks').click(); + $('#tts_pass_asterisks').trigger('change'); + } else { + ttsPassAsterisksCheckbox.checked = true; + $('#tts_pass_asterisks').click(); + $('#tts_pass_asterisks').trigger('change'); + } + + this.onSettingsChange(); + }); + } + + + // 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'; + textNotInsideSelect.disabled = true; + narratorVoiceSelect.disabled = true; + } else if (selectedMethod === 'standard_generation') { + // Enable AT Narrator + atNarratorEnabledSelect.disabled = false; + } + this.settings.at_generation_method = selectedMethod; // Update the setting here + this.onSettingsChange(); // Save the settings after change + }); + } + + // Listener for Language Dropdown + 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 + }); + } + + // Listener for AllTalk Endpoint Input + 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 + }); + } + + } + + //#############################// + // Store ST interface settings // + //#############################// + + onSettingsChange() { + // Update settings based on the UI elements + //this.settings.provider_endpoint = $('#at_server').val(); + this.settings.language = $('#language_options').val(); + //this.settings.voiceMap = $('#voicemap').val(); + this.settings.at_generation_method = $('#at_generation_method').val(); + this.settings.narrator_enabled = $('#at_narrator_enabled').val(); + this.settings.at_narrator_text_not_inside = $('#at_narrator_text_not_inside').val(); + this.settings.rvc_character_voice = $('#rvc_character_voice').val(); + this.settings.rvc_narrator_voice = $('#rvc_narrator_voice').val(); + this.settings.narrator_voice_gen = $('#narrator_voice').val(); + // Save the updated settings + saveTtsProviderSettings(); + } + + //#########################// + // ST Handle Reload button // + //#########################// + + async onRefreshClick() { + try { + updateStatus('Processing'); // Set status to Processing while refreshing + await this.checkReady(); // Check if the TTS provider is ready + await this.loadSettings(this.settings); // Reload the settings + await this.checkReady(); // Check if the TTS provider is ready + updateStatus(this.ready ? 'Ready' : 'Offline'); // Update the status based on readiness + } catch (error) { + console.error('Error during refresh:', error); + updateStatus('Error'); // Set status to Error in case of failure + } + } + + //##################// + // Preview AT Voice // + //##################// + + async previewTtsVoice(voiceName) { + try { + // Prepare data for POST request + const postData = new URLSearchParams(); + postData.append("voice", `${voiceName}`); + // Making the POST request + const response = await fetch(`${this.settings.provider_endpoint}/api/previewvoice/`, { + method: "POST", + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: postData, + }); + if (!response.ok) { + const errorText = await response.text(); + console.error(`[previewTtsVoice] Error Response Text:`, errorText); + throw new Error(`HTTP ${response.status}: ${errorText}`); + } + // Assuming the server returns a URL to the .wav file + const data = await response.json(); + if (data.output_file_url) { + // Prepend the provider endpoint to the output file URL + const fullUrl = `${this.settings.provider_endpoint}${data.output_file_url}`; + // Use an audio element to play the .wav file + const audioElement = new Audio(fullUrl); + 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"); + } + + } catch (error) { + console.error("[previewTtsVoice] Exception caught during preview generation:", error); + throw error; + } + } + + //#####################// + // Populate ST voices // + //#####################// + + async getVoice(voiceName, generatePreview = false) { + // Ensure this.voices is populated + if (this.voices.length === 0) { + // Fetch voice objects logic + } + // Find the object where the name matches voiceName + const match = this.voices.find(voice => voice.name === voiceName); + if (!match) { + // Error handling + } + // Generate preview URL only if requested + if (!match.preview_url && generatePreview) { + // Generate preview logic + } + return match; // Return the found voice object + } + + //##########################################// + // Generate TTS Streaming or call Standard // + //##########################################// + + async generateTts(inputText, voiceId) { + try { + 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); + + // Return the streaming URL directly + return streamingUrl; + } else { + // For standard method + const outputUrl = await this.fetchTtsGeneration(inputText, voiceId); + const audioResponse = await fetch(outputUrl); + if (!audioResponse.ok) { + throw new Error(`HTTP ${audioResponse.status}: Failed to fetch audio data`); + } + return audioResponse; // Return the fetch response directly + } + } catch (error) { + console.error("Error in generateTts:", error); + throw error; + } + } + + + //####################// + // Generate Standard // + //####################// + + async fetchTtsGeneration(inputText, voiceId) { + const requestBody = new URLSearchParams({ + 'text_input': inputText, + 'text_filtering': "standard", + 'character_voice_gen': voiceId, + 'rvccharacter_voice_gen': this.settings.rvccharacter_voice_gen || "Disabled", + 'narrator_enabled': this.settings.narrator_enabled, + 'narrator_voice_gen': this.settings.narrator_voice_gen, + 'rvcnarrator_voice_gen': this.settings.rvcnarrator_voice_gen || "Disabled", + '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" + }).toString(); + + try { + const response = await doExtrasFetch( + `${this.settings.provider_endpoint}/api/tts-generate`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Cache-Control': 'no-cache', + }, + body: requestBody + } + ); + + if (!response.ok) { + const errorText = await response.text(); + console.error(`[fetchTtsGeneration] Error Response Text:`, errorText); + throw new Error(`HTTP ${response.status}: ${errorText}`); + } + const data = await response.json(); + const outputUrl = `${this.settings.provider_endpoint}${data.output_file_url}`; + return outputUrl; // Return only the output_file_url + } catch (error) { + console.error("[fetchTtsGeneration] Exception caught:", error); + throw error; // Rethrow the error for further handling + } + } +} + +//#########################// +// Update Status Messages // +//#########################// + +function updateStatus(message) { + const statusElement = document.getElementById('status_info'); + if (statusElement) { + statusElement.textContent = message; + switch (message) { + case 'Offline': + statusElement.style.color = 'red'; + break; + case 'Ready': + statusElement.style.color = 'lightgreen'; + break; + case 'Processing': + statusElement.style.color = 'blue'; + break; + case 'Error': + statusElement.style.color = 'red'; + break; + } + } +} diff --git a/system/SillyTavern Extension/For AllTalk v1/Instructions.txt b/system/SillyTavern Extension/For AllTalk v1/Instructions.txt new file mode 100644 index 0000000..4673f61 --- /dev/null +++ b/system/SillyTavern Extension/For AllTalk v1/Instructions.txt @@ -0,0 +1 @@ +Place the `alltalk.js` in your `\SillyTavern\public\scripts\extensions\tts\` folder, replacing the existing file. \ No newline at end of file diff --git a/system/SillyTavern Extension/For AllTalk v1/alltalk.js b/system/SillyTavern Extension/For AllTalk v1/alltalk.js new file mode 100644 index 0000000..e2b9a57 --- /dev/null +++ b/system/SillyTavern Extension/For AllTalk v1/alltalk.js @@ -0,0 +1,834 @@ +import { doExtrasFetch, getApiUrl, modules } from '../../extensions.js'; +import { saveTtsProviderSettings } from './index.js'; + +export { AllTalkTtsProvider }; + +class AllTalkTtsProvider { + //########// + // Config // + //########// + + settings = {}; + constructor() { + // Initialize with default settings if they are not already set + this.settings = { + provider_endpoint: this.settings.provider_endpoint || 'http://localhost:7851', + language: this.settings.language || 'en', + voiceMap: this.settings.voiceMap || {}, + at_generation_method: this.settings.at_generation_method || 'standard_generation', + narrator_enabled: this.settings.narrator_enabled || 'false', + at_narrator_text_not_inside: this.settings.at_narrator_text_not_inside || 'narrator', + narrator_voice_gen: this.settings.narrator_voice_gen || 'female_01.wav', + finetuned_model: this.settings.finetuned_model || 'false' + }; + // Separate property for dynamically updated settings from the server + this.dynamicSettings = { + modelsAvailable: [], + currentModel: '', + deepspeed_available: false, + deepSpeedEnabled: false, + lowVramEnabled: false, + }; + } + ready = false; + voices = []; + separator = '. '; + audioElement = document.createElement('audio'); + + languageLabels = { + 'Arabic': 'ar', + 'Brazilian Portuguese': 'pt', + 'Chinese': 'zh-cn', + 'Czech': 'cs', + 'Dutch': 'nl', + 'English': 'en', + 'French': 'fr', + 'German': 'de', + 'Italian': 'it', + 'Polish': 'pl', + 'Russian': 'ru', + 'Spanish': 'es', + 'Turkish': 'tr', + 'Japanese': 'ja', + 'Korean': 'ko', + 'Hungarian': 'hu', + 'Hindi': 'hi', + }; + + get settingsHtml() { + let html = `
AllTalk Settings
`; + + html += `
+ +
+ + +
+
`; + + html += `
+ +
+ + +
+ +
+ + +
+ +
`; + + html += `
+
+ + +
+
+ + +
+
`; + + + html += `
+
+ + +
+ +
+ + +
+
`; + + + html += `
+
+ + +
+
+ + +
+
+ Status: Ready +
+
+ +
+
`; + + + html += `
+
+ AllTalk Config & Docs. +
+ +
+ AllTalk Website. +
+
`; + + html += `
+
+Text-generation-webui users - Uncheck Enable TTS in Text-generation-webui. +
+
`; + + return html; + } + + + //#################// + // Startup ST & AT // + //#################// + + async loadSettings(settings) { + updateStatus('Offline'); + + if (Object.keys(settings).length === 0) { + console.info('Using default AllTalk TTS Provider settings'); + } else { + // Populate settings with provided values, ignoring server-provided settings + for (const key in settings) { + if (key in this.settings) { + this.settings[key] = settings[key]; + } else { + console.debug(`Ignoring non-user-configurable setting: ${key}`); + } + } + } + + // Update UI elements to reflect the loaded settings + $('#at_server').val(this.settings.provider_endpoint); + $('#language_options').val(this.settings.language); + //$('#voicemap').val(this.settings.voiceMap); + $('#at_generation_method').val(this.settings.at_generation_method); + $('#at_narrator_enabled').val(this.settings.narrator_enabled); + $('#at_narrator_text_not_inside').val(this.settings.at_narrator_text_not_inside); + $('#narrator_voice').val(this.settings.narrator_voice_gen); + + console.debug('AllTalkTTS: Settings loaded'); + try { + // Check if TTS provider is ready + await this.checkReady(); + await this.updateSettingsFromServer(); // Fetch dynamic settings from the TTS server + await this.fetchTtsVoiceObjects(); // Fetch voices only if service is ready + this.updateNarratorVoicesDropdown(); + this.updateLanguageDropdown(); + this.setupEventListeners(); + this.applySettingsToHTML(); + updateStatus('Ready'); + } catch (error) { + console.error("Error loading settings:", error); + updateStatus('Offline'); + } + } + + applySettingsToHTML() { + // Apply loaded settings or use defaults + const narratorVoiceSelect = document.getElementById('narrator_voice'); + const atNarratorSelect = document.getElementById('at_narrator_enabled'); + const textNotInsideSelect = document.getElementById('at_narrator_text_not_inside'); + const generationMethodSelect = document.getElementById('at_generation_method'); + this.settings.narrator_voice = this.settings.narrator_voice_gen; + // Apply settings to Narrator Voice dropdown + if (narratorVoiceSelect && this.settings.narrator_voice) { + narratorVoiceSelect.value = this.settings.narrator_voice.replace('.wav', ''); + } + // Apply settings to AT Narrator Enabled dropdown + if (atNarratorSelect) { + // Sync the state with the checkbox in index.js + const ttsPassAsterisksCheckbox = document.getElementById('tts_pass_asterisks'); // Access the checkbox from index.js + const ttsNarrateQuotedCheckbox = document.getElementById('tts_narrate_quoted'); // Access the checkbox from index.js + const ttsNarrateDialoguesCheckbox = document.getElementById('tts_narrate_dialogues'); // Access the checkbox from index.js + // Sync the state with the checkbox in index.js + if (this.settings.narrator_enabled) { + ttsPassAsterisksCheckbox.checked = false; + $('#tts_pass_asterisks').click(); // Simulate a click event + $('#tts_pass_asterisks').trigger('change'); + } + if (!this.settings.narrator_enabled) { + ttsPassAsterisksCheckbox.checked = true; + $('#tts_pass_asterisks').click(); // Simulate a click event + $('#tts_pass_asterisks').trigger('change'); + } + // Uncheck and set tts_narrate_quoted to false if narrator is enabled + if (this.settings.narrator_enabledd) { + ttsNarrateQuotedCheckbox.checked = true; + ttsNarrateDialoguesCheckbox.checked = true; + // Trigger click events instead of change events + $('#tts_narrate_quoted').click(); + $('#tts_narrate_quoted').trigger('change'); + $('#tts_narrate_dialogues').click(); + $('#tts_narrate_dialogues').trigger('change'); + } + atNarratorSelect.value = this.settings.narrator_enabled.toString(); + this.settings.narrator_enabled = this.settings.narrator_enabled.toString(); + } + // Apply settings to the Language dropdown + const languageSelect = document.getElementById('language_options'); + if (languageSelect && this.settings.language) { + languageSelect.value = this.settings.language; + } + // Apply settings to Text Not Inside dropdown + if (textNotInsideSelect && this.settings.text_not_inside) { + textNotInsideSelect.value = this.settings.text_not_inside; + this.settings.at_narrator_text_not_inside = this.settings.text_not_inside; + } + // Apply settings to Generation Method dropdown + if (generationMethodSelect && this.settings.at_generation_method) { + generationMethodSelect.value = this.settings.at_generation_method; + } + // Additional logic to disable/enable dropdowns based on the selected generation method + const isStreamingEnabled = this.settings.at_generation_method === 'streaming_enabled'; + if (isStreamingEnabled) { + // Disable certain dropdowns when streaming is enabled + if (atNarratorSelect) atNarratorSelect.disabled = true; + if (textNotInsideSelect) textNotInsideSelect.disabled = true; + if (narratorVoiceSelect) narratorVoiceSelect.disabled = true; + } else { + // Enable dropdowns for standard generation + if (atNarratorSelect) atNarratorSelect.disabled = false; + if (textNotInsideSelect) textNotInsideSelect.disabled = !this.settings.narrator_enabled; + if (narratorVoiceSelect) narratorVoiceSelect.disabled = !this.settings.narrator_enabled; + } + const modelSelect = document.getElementById('switch_model'); + if (this.settings.finetuned_model === 'true') { + const ftOption = document.createElement('option'); + ftOption.value = 'XTTSv2 FT'; + ftOption.textContent = 'XTTSv2 FT'; + modelSelect.appendChild(ftOption); + } + } + + //##############################// + // Check AT Server is Available // + //##############################// + + async checkReady() { + try { + const response = await fetch(`${this.settings.provider_endpoint}/api/ready`); + // Check if the HTTP request was successful + if (!response.ok) { + throw new Error(`HTTP Error Response: ${response.status} ${response.statusText}`); + } + const statusText = await response.text(); + // Check if the response is 'Ready' + if (statusText === 'Ready') { + this.ready = true; // Set the ready flag to true + console.log('TTS service is ready.'); + } else { + this.ready = false; + console.log('TTS service is not ready.'); + } + } catch (error) { + console.error('Error checking TTS service readiness:', error); + this.ready = false; // Ensure ready flag is set to false in case of error + } + } + + //######################// + // Get Available Voices // + //######################// + + async fetchTtsVoiceObjects() { + const response = await fetch(`${this.settings.provider_endpoint}/api/voices`); + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`HTTP ${response.status}: ${errorText}`); + } + const data = await response.json(); + const voices = data.voices.map(filename => { + const voiceName = filename.replace('.wav', ''); + return { + name: voiceName, + voice_id: voiceName, + preview_url: null, // Preview URL will be dynamically generated + lang: 'en' // Default language + }; + }); + this.voices = voices; // Assign to the class property + return voices; // Also return this list + } + + //##########################################// + // Get Current AT Server Config & Update ST // + //##########################################// + + async updateSettingsFromServer() { + try { + // Fetch current settings + const response = await fetch(`${this.settings.provider_endpoint}/api/currentsettings`); + if (!response.ok) { + throw new Error(`Failed to fetch current settings: ${response.statusText}`); + } + const currentSettings = await response.json(); + // Update internal settings + this.settings.modelsAvailable = currentSettings.models_available; + this.settings.currentModel = currentSettings.current_model_loaded; + this.settings.deepspeed_available = currentSettings.deepspeed_available; + this.settings.deepSpeedEnabled = currentSettings.deepspeed_status; + this.settings.lowVramEnabled = currentSettings.low_vram_status; + this.settings.finetuned_model = currentSettings.finetuned_model + // Update HTML elements + this.updateModelDropdown(); + this.updateCheckboxes(); + } catch (error) { + console.error(`Error updating settings from server: ${error}`); + } + } + + //###################################################// + // Get Current AT Server Config & Update ST (Models) // + //###################################################// + + updateModelDropdown() { + const modelSelect = document.getElementById('switch_model'); + if (modelSelect) { + modelSelect.innerHTML = ''; // Clear existing options + this.settings.modelsAvailable.forEach(model => { + const option = document.createElement('option'); + option.value = model.model_name; + option.textContent = model.model_name; // Use model_name instead of name + option.selected = model.model_name === this.settings.currentModel; + modelSelect.appendChild(option); + }); + } + } + + //#######################################################// + // Get Current AT Server Config & Update ST (DS and LVR) // + //#######################################################// + + updateCheckboxes() { + const deepspeedCheckbox = document.getElementById('deepspeed'); + const lowVramCheckbox = document.getElementById('low_vram'); + if (lowVramCheckbox) lowVramCheckbox.checked = this.settings.lowVramEnabled; + if (deepspeedCheckbox) { + deepspeedCheckbox.checked = this.settings.deepSpeedEnabled; + deepspeedCheckbox.disabled = !this.settings.deepspeed_available; // Disable checkbox if deepspeed is not available + } + } + + //###############################################################// + // Get Current AT Server Config & Update ST (AT Narrator Voices) // + //###############################################################// + + updateNarratorVoicesDropdown() { + const narratorVoiceSelect = document.getElementById('narrator_voice'); + if (narratorVoiceSelect && this.voices) { + // Clear existing options + narratorVoiceSelect.innerHTML = ''; + // Add new options + for (let voice of this.voices) { + const option = document.createElement('option'); + option.value = voice.voice_id; + option.textContent = voice.name; + narratorVoiceSelect.appendChild(option); + } + } + } + + //######################################################// + // Get Current AT Server Config & Update ST (Languages) // + //######################################################// + + updateLanguageDropdown() { + const languageSelect = document.getElementById('language_options'); + if (languageSelect) { + // Ensure default language is set + this.settings.language = this.settings.language; + + languageSelect.innerHTML = ''; + for (let language in this.languageLabels) { + const option = document.createElement('option'); + option.value = this.languageLabels[language]; + option.textContent = language; + if (this.languageLabels[language] === this.settings.language) { + option.selected = true; + } + languageSelect.appendChild(option); + } + } + } + + //########################################// + // Start AT TTS extenstion page listeners // + //########################################// + + setupEventListeners() { + + let debounceTimeout; + const debounceDelay = 500; // Milliseconds + + // Define the event handler function + const onModelSelectChange = async (event) => { + console.log("Model select change event triggered"); // Debugging statement + const selectedModel = event.target.value; + console.log(`Selected model: ${selectedModel}`); // Debugging statement + // Set status to Processing + updateStatus('Processing'); + try { + const response = await fetch(`${this.settings.provider_endpoint}/api/reload?tts_method=${encodeURIComponent(selectedModel)}`, { + 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 + updateStatus('Ready'); + } catch (error) { + console.error("POST request error:", error); // Debugging statement + // Set status to Error in case of failure + updateStatus('Error'); + } + + // Handle response or error + }; + + 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 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' + }); + 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 + updateStatus('Ready'); + } catch (error) { + console.error("POST request error:", error); // Debugging statement + // Set status to Error in case of failure + updateStatus('Error'); + } + }); + } + + // Low VRAM Listener + const lowVramCheckbox = document.getElementById('low_vram'); + if (lowVramCheckbox) { + lowVramCheckbox.addEventListener('change', 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' + }); + 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 + updateStatus('Ready'); + } catch (error) { + console.error("POST request error:", error); // Debugging statement + // Set status to Error in case of failure + updateStatus('Error'); + } + }); + } + + // Narrator Voice Dropdown Listener + const narratorVoiceSelect = document.getElementById('narrator_voice'); + if (narratorVoiceSelect) { + narratorVoiceSelect.addEventListener('change', (event) => { + this.settings.narrator_voice_gen = `${event.target.value}.wav`; + this.onSettingsChange(); // Save the settings after change + }); + } + + const textNotInsideSelect = document.getElementById('at_narrator_text_not_inside'); + if (textNotInsideSelect) { + textNotInsideSelect.addEventListener('change', (event) => { + this.settings.text_not_inside = event.target.value; + this.onSettingsChange(); // Save the settings after change + }); + } + + // AT Narrator Dropdown Listener + const atNarratorSelect = document.getElementById('at_narrator_enabled'); + const ttsPassAsterisksCheckbox = document.getElementById('tts_pass_asterisks'); // Access the checkbox from index.js + const ttsNarrateQuotedCheckbox = document.getElementById('tts_narrate_quoted'); // Access the checkbox from index.js + const ttsNarrateDialoguesCheckbox = document.getElementById('tts_narrate_dialogues'); // Access the checkbox from index.js + + if (atNarratorSelect && textNotInsideSelect && narratorVoiceSelect) { + atNarratorSelect.addEventListener('change', (event) => { + const isNarratorEnabled = event.target.value === 'true'; + this.settings.narrator_enabled = isNarratorEnabled; // Update the setting here + textNotInsideSelect.disabled = !isNarratorEnabled; + narratorVoiceSelect.disabled = !isNarratorEnabled; + + // Sync the state with the checkbox in index.js + if (isNarratorEnabled) { + ttsPassAsterisksCheckbox.checked = false; + $('#tts_pass_asterisks').click(); // Simulate a click event + $('#tts_pass_asterisks').trigger('change'); + } + if (!isNarratorEnabled) { + ttsPassAsterisksCheckbox.checked = true; + $('#tts_pass_asterisks').click(); // Simulate a click event + $('#tts_pass_asterisks').trigger('change'); + } + // Uncheck and set tts_narrate_quoted to false if narrator is enabled + if (isNarratorEnabled) { + ttsNarrateQuotedCheckbox.checked = true; + ttsNarrateDialoguesCheckbox.checked = true; + // Trigger click events instead of change events + $('#tts_narrate_quoted').click(); + $('#tts_narrate_quoted').trigger('change'); + $('#tts_narrate_dialogues').click(); + $('#tts_narrate_dialogues').trigger('change'); + } + this.onSettingsChange(); // Save the settings after change + }); + } + + + // 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'; + textNotInsideSelect.disabled = true; + narratorVoiceSelect.disabled = true; + } else if (selectedMethod === 'standard_generation') { + // Enable AT Narrator + atNarratorEnabledSelect.disabled = false; + } + this.settings.at_generation_method = selectedMethod; // Update the setting here + this.onSettingsChange(); // Save the settings after change + }); + } + + // Listener for Language Dropdown + 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 + }); + } + + // Listener for AllTalk Endpoint Input + 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 + }); + } + + } + + //#############################// + // Store ST interface settings // + //#############################// + + onSettingsChange() { + // Update settings based on the UI elements + //this.settings.provider_endpoint = $('#at_server').val(); + this.settings.language = $('#language_options').val(); + //this.settings.voiceMap = $('#voicemap').val(); + this.settings.at_generation_method = $('#at_generation_method').val(); + this.settings.narrator_enabled = $('#at_narrator_enabled').val(); + this.settings.at_narrator_text_not_inside = $('#at_narrator_text_not_inside').val(); + this.settings.narrator_voice_gen = $('#narrator_voice').val(); + // Save the updated settings + saveTtsProviderSettings(); + } + + //#########################// + // ST Handle Reload button // + //#########################// + + async onRefreshClick() { + await this.checkReady(); // Check if the TTS provider is ready + await this.loadSettings(this.settings); // Reload the settings + // Additional actions as needed + } + + //##################// + // Preview AT Voice // + //##################// + + async previewTtsVoice(voiceName) { + try { + // Prepare data for POST request + const postData = new URLSearchParams(); + postData.append("voice", `${voiceName}.wav`); + // Making the POST request + const response = await fetch(`${this.settings.provider_endpoint}/api/previewvoice/`, { + method: "POST", + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: postData, + }); + if (!response.ok) { + const errorText = await response.text(); + console.error(`[previewTtsVoice] Error Response Text:`, errorText); + throw new Error(`HTTP ${response.status}: ${errorText}`); + } + // Assuming the server returns a URL to the .wav file + const data = await response.json(); + if (data.output_file_url) { + // Use an audio element to play the .wav file + const audioElement = new Audio(`${this.settings.provider_endpoint}${data.output_file_url}`); + 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"); + } + + } catch (error) { + console.error("[previewTtsVoice] Exception caught during preview generation:", error); + throw error; + } + } + + //#####################// + // Populate ST voices // + //#####################// + + async getVoice(voiceName, generatePreview = false) { + // Ensure this.voices is populated + if (this.voices.length === 0) { + // Fetch voice objects logic + } + // Find the object where the name matches voiceName + const match = this.voices.find(voice => voice.name === voiceName); + if (!match) { + // Error handling + } + // Generate preview URL only if requested + if (!match.preview_url && generatePreview) { + // Generate preview logic + } + return match; // Return the found voice object + } + + //##########################################// + // Generate TTS Streaming or call Standard // + //##########################################// + + async generateTts(inputText, voiceId) { + try { + 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)}.wav&language=${encodeURIComponent(this.settings.language)}&output_file=stream_output.wav`; + console.log("Streaming URL:", streamingUrl); + + // Return the streaming URL directly + return streamingUrl; + } else { + // For standard method + const outputUrl = await this.fetchTtsGeneration(inputText, voiceId); + const audioResponse = await fetch(outputUrl); + if (!audioResponse.ok) { + throw new Error(`HTTP ${audioResponse.status}: Failed to fetch audio data`); + } + return audioResponse; // Return the fetch response directly + } + } catch (error) { + console.error("Error in generateTts:", error); + throw error; + } + } + + + //####################// + // Generate Standard // + //####################// + + async fetchTtsGeneration(inputText, voiceId) { + // Prepare the request payload + const requestBody = new URLSearchParams({ + 'text_input': inputText, + 'text_filtering': "standard", + 'character_voice_gen': voiceId + ".wav", + 'narrator_enabled': this.settings.narrator_enabled, + 'narrator_voice_gen': this.settings.narrator_voice_gen + ".wav", + '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" + }).toString(); + + try { + const response = await doExtrasFetch( + `${this.settings.provider_endpoint}/api/tts-generate`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Cache-Control': 'no-cache', + }, + body: requestBody + } + ); + + if (!response.ok) { + const errorText = await response.text(); + console.error(`[fetchTtsGeneration] Error Response Text:`, errorText); + // toastr.error(response.statusText, 'TTS Generation Failed'); + throw new Error(`HTTP ${response.status}: ${errorText}`); + } + const data = await response.json(); + //const outputUrl = data.output_file_url; + const outputUrl = `${this.settings.provider_endpoint}${data.output_file_url}`; + return outputUrl; // Return only the output_file_url + } catch (error) { + console.error("[fetchTtsGeneration] Exception caught:", error); + throw error; // Rethrow the error for further handling + } + } +} + +//#########################// +// Update Status Messages // +//#########################// + +function updateStatus(message) { + const statusElement = document.getElementById('status_info'); + if (statusElement) { + statusElement.textContent = message; + switch (message) { + case 'Offline': + statusElement.style.color = 'red'; + break; + case 'Ready': + statusElement.style.color = 'lightgreen'; + break; + case 'Processing': + statusElement.style.color = 'blue'; + break; + case 'Error': + statusElement.style.color = 'red'; + break; + } + } +} \ No newline at end of file diff --git a/system/TGWUI Extension/AT TGWUI Remote Instructions.txt b/system/TGWUI Extension/AT TGWUI Remote Instructions.txt new file mode 100644 index 0000000..3e98301 --- /dev/null +++ b/system/TGWUI Extension/AT TGWUI Remote Instructions.txt @@ -0,0 +1,17 @@ +Text-generation-webui remote extension for AllTalk! + +This folder contains everything you need to set up a Text-generation-webui extension that can communicate with an AllTalk server remotely. + +To get started, follow these simple steps: + +1) In your Text-generation-webui's extensions folder, create a new folder and name it "alltalk_tts". +2) Take all the files from this current folder and copy them into the newly created "alltalk_tts" folder. +3) Start & Open Text-generation-webui and look for "alltalk_tts" in the list of extensions. Select alltalk_tts, save your settings and restart Text-generation-webui. +4) Text-generation-webui will restart and load the AllTalk extension. You may see an error/warning as the extension will not yet have the IP address details of your remote AllTalk server. +5) In the extension settings, enter the correct IP address and port of your AllTalk server. If your AllTalk server is running on a remote machine accessed through a secure tunnel, make sure to use "https" instead of "http". +6) After entering the server details, click on the "Refresh settings & voices" button. If the settings are correct, the extension will download all the available settings from the AllTalk server. +7) The first time you successfully generate speech using the extension, the settings will be automatically saved to a local configuration file. + +Please note that if the AllTalk server you're connected to doesn't support certain settings/features, those options will be greyed out and unavailable in the extension. + +Please use the "Refresh settings & voices" button to make sure the extension is in sync with your AllTalk server. \ No newline at end of file diff --git a/system/TGWUI Extension/deepspeeddisabled.wav b/system/TGWUI Extension/deepspeeddisabled.wav new file mode 100644 index 0000000..e276b47 Binary files /dev/null and b/system/TGWUI Extension/deepspeeddisabled.wav differ diff --git a/system/TGWUI Extension/deepspeedenabled.wav b/system/TGWUI Extension/deepspeedenabled.wav new file mode 100644 index 0000000..e711b0f Binary files /dev/null and b/system/TGWUI Extension/deepspeedenabled.wav differ diff --git a/system/TGWUI Extension/harvard_sentences.txt b/system/TGWUI Extension/harvard_sentences.txt new file mode 100644 index 0000000..958d7f3 --- /dev/null +++ b/system/TGWUI Extension/harvard_sentences.txt @@ -0,0 +1,720 @@ +The birch canoe slid on the smooth planks. +Glue the sheet to the dark blue background. +It's easy to tell the depth of a well. +These days a chicken leg is a rare dish. +Rice is often served in round bowls. +The juice of lemons makes fine punch. +The box was thrown beside the parked truck. +The hogs were fed chopped corn and garbage. +Four hours of steady work faced us. +A large size in stockings is hard to sell. +The boy was there when the sun rose. +A rod is used to catch pink salmon. +The source of the huge river is the clear spring. +Kick the ball straight and follow through. +Help the woman get back to her feet. +A pot of tea helps to pass the evening. +Smoky fires lack flame and heat. +The soft cushion broke the man's fall. +The salt breeze came across from the sea. +The girl at the booth sold fifty bonds. +The small pup gnawed a hole in the sock. +The fish twisted and turned on the bent hook. +Press the pants and sew a button on the vest. +The swan dive was far short of perfect. +The beauty of the view stunned the young boy. +Two blue fish swam in the tank. +Her purse was full of useless trash. +The colt reared and threw the tall rider. +It snowed, rained, and hailed the same morning. +Read verse out loud for pleasure. +Hoist the load to your left shoulder. +Take the winding path to reach the lake. +Note closely the size of the gas tank. +Wipe the grease off his dirty face. +Mend the coat before you go out. +The wrist was badly strained and hung limp. +The stray cat gave birth to kittens. +The young girl gave no clear response. +The meal was cooked before the bell rang. +What joy there is in living. +A king ruled the state in the early days. +The ship was torn apart on the sharp reef. +Sickness kept him home the third week. +The wide road shimmered in the hot sun. +The lazy cow lay in the cool grass. +Lift the square stone over the fence. +The rope will bind the seven books at once. +Hop over the fence and plunge in. +The friendly gang left the drug store. +Mesh wire keeps chicks inside. +The frosty air passed through the coat. +The crooked maze failed to fool the mouse. +Adding fast leads to wrong sums. +The show was a flop from the very start. +A saw is a tool used for making boards. +The wagon moved on well oiled wheels. +March the soldiers past the next hill. +A cup of sugar makes sweet fudge. +Place a rosebush near the porch steps. +Both lost their lives in the raging storm. +We talked of the side show in the circus. +Use a pencil to write the first draft. +He ran half way to the hardware store. +The clock struck to mark the third period. +A small creek cut across the field. +Cars and busses stalled in snow drifts. +The set of china hit the floor with a crash. +This is a grand season for hikes on the road. +The dune rose from the edge of the water. +Those words were the cue for the actor to leave. +A yacht slid around the point into the bay. +The two met while playing on the sand. +The ink stain dried on the finished page. +The walled town was seized without a fight. +The lease ran out in sixteen weeks. +A tame squirrel makes a nice pet. +The horn of the car woke the sleeping cop. +The heart beat strongly and with firm strokes. +The pearl was worn in a thin silver ring. +The fruit peel was cut in thick slices. +The Navy attacked the big task force. +See the cat glaring at the scared mouse. +There are more than two factors here. +The hat brim was wide and too droopy. +The lawyer tried to lose his case. +The grass curled around the fence post. +Cut the pie into large parts. +Men strive but seldom get rich. +Always close the barn door tight. +He lay prone and hardly moved a limb. +The slush lay deep along the street. +A wisp of cloud hung in the blue air. +A pound of sugar costs more than eggs. +The fin was sharp and cut the clear water. +The play seems dull and quite stupid. +Bail the boat to stop it from sinking. +The term ended in late June that year. +A tusk is used to make costly gifts. +Ten pins were set in order. +The bill was paid every third week. +Oak is strong and also gives shade. +Cats and dogs each hate the other. +The pipe began to rust while new. +Open the crate but don't break the glass. +Add the sum to the product of these three. +Thieves who rob friends deserve jail. +The ripe taste of cheese improves with age. +Act on these orders with great speed. +The hog crawled under the high fence. +Move the vat over the hot fire. +The bark of the pine tree was shiny and dark. +Leaves turn brown and yellow in the fall. +The pennant waved when the wind blew. +Split the log with a quick, sharp blow. +Burn peat after the logs give out. +He ordered peach pie with ice cream. +Weave the carpet on the right hand side. +Hemp is a weed found in parts of the tropics. +A lame back kept his score low. +We find joy in the simplest things. +Type out three lists of orders. +The harder he tried the less he got done. +The boss ran the show with a watchful eye. +The cup cracked and spilled its contents. +Paste can cleanse the most dirty brass. +The slang word for raw whiskey is booze. +It caught its hind paw in a rusty trap. +The wharf could be seen at the farther shore. +Feel the heat of the weak dying flame. +The tiny girl took off her hat. +A cramp is no small danger on a swim. +He said the same phrase thirty times. +Pluck the bright rose without leaves. +Two plus seven is less than ten. +The glow deepened in the eyes of the sweet girl. +Bring your problems to the wise chief. +Write a fond note to the friend you cherish. +Clothes and lodging are free to new men. +We frown when events take a bad turn. +Port is a strong wine with a smoky taste. +The young kid jumped the rusty gate. +Guess the results from the first scores. +A salt pickle tastes fine with ham. +The just claim got the right verdict. +These thistles bend in a high wind. +Pure bred poodles have curls. +The tree top waved in a graceful way. +The spot on the blotter was made by green ink. +Mud was spattered on the front of his white shirt. +The cigar burned a hole in the desk top. +The empty flask stood on the tin tray. +A speedy man can beat this track mark. +He broke a new shoelace that day. +The coffee stand is too high for the couch. +The urge to write short stories is rare. +The pencils have all been used. +The pirates seized the crew of the lost ship. +We tried to replace the coin but failed. +She sewed the torn coat quite neatly. +The sofa cushion is red and of light weight. +The jacket hung on the back of the wide chair. +At that high level the air is pure. +Drop the two when you add the figures. +A filing case is now hard to buy. +An abrupt start does not win the prize. +Wood is best for making toys and blocks. +The office paint was a dull, sad tan. +He knew the skill of the great young actress. +A rag will soak up spilled water. +A shower of dirt fell from the hot pipes. +Steam hissed from the broken valve. +The child almost hurt the small dog. +There was a sound of dry leaves outside. +The sky that morning was clear and bright blue. +Torn scraps littered the stone floor. +Sunday is the best part of the week. +The doctor cured him with these pills. +The new girl was fired today at noon. +They felt gay when the ship arrived in port. +Add the store's account to the last cent. +Acid burns holes in wool cloth. +Fairy tales should be fun to write. +Eight miles of woodland burned to waste. +The third act was dull and tired the players. +A young child should not suffer fright. +Add the column and put the sum here. +We admire and love a good cook. +There the flood mark is ten inches. +He carved a head from the round block of marble. +She has a smart way of wearing clothes. +The fruit of a fig tree is apple-shaped. +Corn cobs can be used to kindle a fire. +Where were they when the noise started. +The paper box is full of thumb tacks. +Sell your gift to a buyer at a good gain. +The tongs lay beside the ice pail. +The petals fall with the next puff of wind. +Bring your best compass to the third class. +They could laugh although they were sad. +Farmers came in to thresh the oat crop. +The brown house was on fire to the attic. +The lure is used to catch trout and flounder. +Float the soap on top of the bath water. +A blue crane is a tall wading bird. +A fresh start will work such wonders. +The club rented the rink for the fifth night. +After the dance, they went straight home. +The hostess taught the new maid to serve. +He wrote his last novel there at the inn. +Even the worst will beat his low score. +The cement had dried when he moved it. +The loss of the second ship was hard to take. +The fly made its way along the wall. +Do that with a wooden stick. +Live wires should be kept covered. +The large house had hot water taps. +It is hard to erase blue or red ink. +Write at once or you may forget it. +The doorknob was made of bright clean brass. +The wreck occurred by the bank on Main Street. +A pencil with black lead writes best. +Coax a young calf to drink from a bucket. +Schools for ladies teach charm and grace. +The lamp shone with a steady green flame. +They took the axe and the saw to the forest. +The ancient coin was quite dull and worn. +The shaky barn fell with a loud crash. +Jazz and swing fans like fast music. +Rake the rubbish up and then burn it. +Slash the gold cloth into fine ribbons. +Try to have the court decide the case. +They are pushed back each time they attack. +He broke his ties with groups of former friends. +They floated on the raft to sun their white backs. +The map had an X that meant nothing. +Whitings are small fish caught in nets. +Some ads serve to cheat buyers. +Jerk the rope and the bell rings weakly. +A waxed floor makes us lose balance. +Madam, this is the best brand of corn. +On the islands the sea breeze is soft and mild. +The play began as soon as we sat down. +This will lead the world to more sound and fury. +Add salt before you fry the egg. +The rush for funds reached its peak Tuesday. +The birch looked stark white and lonesome. +The box is held by a bright red snapper. +To make pure ice, you freeze water. +The first worm gets snapped early. +Jump the fence and hurry up the bank. +Yell and clap as the curtain slides back. +They are men who walk the middle of the road. +Both brothers wear the same size. +In some form or other we need fun. +The prince ordered his head chopped off. +The houses are built of red clay bricks. +Ducks fly north but lack a compass. +Fruit flavors are used in fizz drinks. +These pills do less good than others. +Canned pears lack full flavor. +The dark pot hung in the front closet. +Carry the pail to the wall and spill it there. +The train brought our hero to the big town. +We are sure that one war is enough. +Gray paint stretched for miles around. +The rude laugh filled the empty room. +High seats are best for football fans. +Tea served from the brown jug is tasty. +A dash of pepper spoils beef stew. +A zestful food is the hot-cross bun. +The horse trotted around the field at a brisk pace. +Find the twin who stole the pearl necklace. +Cut the cord that binds the box tightly. +The red tape bound the smuggled food. +Look in the corner to find the tan shirt. +The cold drizzle will halt the bond drive. +Nine men were hired to dig the ruins. +The junk yard had a mouldy smell. +The flint sputtered and lit a pine torch. +Soak the cloth and drown the sharp odor. +The shelves were bare of both jam or crackers. +A joy to every child is the swan boat. +All sat frozen and watched the screen. +A cloud of dust stung his tender eyes. +To reach the end he needs much courage. +Shape the clay gently into block form. +A ridge on a smooth surface is a bump or flaw. +Hedge apples may stain your hands green. +Quench your thirst, then eat the crackers. +Tight curls get limp on rainy days. +The mute muffled the high tones of the horn. +The gold ring fits only a pierced ear. +The old pan was covered with hard fudge. +Watch the log float in the wide river. +The node on the stalk of wheat grew daily. +The heap of fallen leaves was set on fire. +Write fast if you want to finish early. +His shirt was clean but one button was gone. +The barrel of beer was a brew of malt and hops. +Tin cans are absent from store shelves. +Slide the box into that empty space. +The plant grew large and green in the window. +The beam dropped down on the workmen's head. +Pink clouds floated with the breeze. +She danced like a swan, tall and graceful. +The tube was blown and the tire flat and useless. +It is late morning on the old wall clock. +Let's all join as we sing the last chorus. +The last switch cannot be turned off. +The fight will end in just six minutes. +The store walls were lined with colored frocks. +The peace league met to discuss their plans. +The rise to fame of a person takes luck. +Paper is scarce, so write with much care. +The quick fox jumped on the sleeping cat. +The nozzle of the fire hose was bright brass. +Screw the round cap on as tight as needed. +Time brings us many changes. +The purple tie was ten years old. +Men think and plan and sometimes act. +Fill the ink jar with sticky glue. +He smoke a big pipe with strong contents. +We need grain to keep our mules healthy. +Pack the records in a neat thin case. +The crunch of feet in the snow was the only sound. +The copper bowl shone in the sun's rays. +Boards will warp unless kept dry. +The plush chair leaned against the wall. +Glass will clink when struck by metal. +Bathe and relax in the cool green grass. +Nine rows of soldiers stood in line. +The beach is dry and shallow at low tide. +The idea is to sew both edges straight. +The kitten chased the dog down the street. +Pages bound in cloth make a book. +Try to trace the fine lines of the painting. +Women form less than half of the group. +The zones merge in the central part of town. +A gem in the rough needs work to polish. +Code is used when secrets are sent. +Most of the news is easy for us to hear. +He used the lathe to make brass objects. +The vane on top of the pole revolved in the wind. +Mince pie is a dish served to children. +The clan gathered on each dull night. +Let it burn, it gives us warmth and comfort. +A castle built from sand fails to endure. +A child's wit saved the day for us. +Tack the strip of carpet to the worn floor. +Next Tuesday we must vote. +Pour the stew from the pot into the plate. +Each penny shone like new. +The man went to the woods to gather sticks. +The dirt piles were lines along the road. +The logs fell and tumbled into the clear stream. +Just hoist it up and take it away. +A ripe plum is fit for a king's palate. +Our plans right now are hazy. +Brass rings are sold by these natives. +It takes a good trap to capture a bear. +Feed the white mouse some flower seeds. +The thaw came early and freed the stream. +He took the lead and kept it the whole distance. +The key you designed will fit the lock. +Plead to the council to free the poor thief. +Better hash is made of rare beef. +This plank was made for walking on. +The lake sparkled in the red hot sun. +He crawled with care along the ledge. +Tend the sheep while the dog wanders. +It takes a lot of help to finish these. +Mark the spot with a sign painted red. +Take two shares as a fair profit. +The fur of cats goes by many names. +North winds bring colds and fevers. +He asks no person to vouch for him. +Go now and come here later. +A sash of gold silk will trim her dress. +Soap can wash most dirt away. +That move means the game is over. +He wrote down a long list of items. +A siege will crack the strong defense. +Grape juice and water mix well. +Roads are paved with sticky tar. +Fake stones shine but cost little. +The drip of the rain made a pleasant sound. +Smoke poured out of every crack. +Serve the hot rum to the tired heroes. +Much of the story makes good sense. +The sun came up to light the eastern sky. +Heave the line over the port side. +A lathe cuts and trims any wood. +It's a dense crowd in two distinct ways. +His hip struck the knee of the next player. +The stale smell of old beer lingers. +The desk was firm on the shaky floor. +It takes heat to bring out the odor. +Beef is scarcer than some lamb. +Raise the sail and steer the ship northward. +A cone costs five cents on Mondays. +A pod is what peas always grow in. +Jerk the dart from the cork target. +No cement will hold hard wood. +We now have a new base for shipping. +A list of names is carved around the base. +The sheep were led home by a dog. +Three for a dime, the young peddler cried. +The sense of smell is better than that of touch. +No hardship seemed to keep him sad. +Grace makes up for lack of beauty. +Nudge gently but wake her now. +The news struck doubt into restless minds. +Once we stood beside the shore. +A chink in the wall allowed a draft to blow. +Fasten two pins on each side. +A cold dip restores health and zest. +He takes the oath of office each March. +The sand drifts over the sill of the old house. +The point of the steel pen was bent and twisted. +There is a lag between thought and act. +Seed is needed to plant the spring corn. +Draw the chart with heavy black lines. +The boy owed his pal thirty cents. +The chap slipped into the crowd and was lost. +Hats are worn to tea and not to dinner. +The ramp led up to the wide highway. +Beat the dust from the rug onto the lawn. +Say it slowly but make it ring clear. +The straw nest housed five robins. +Screen the porch with woven straw mats. +This horse will nose his way to the finish. +The dry wax protects the deep scratch. +He picked up the dice for a second roll. +These coins will be needed to pay his debt. +The nag pulled the frail cart along. +Twist the valve and release hot steam. +The vamp of the shoe had a gold buckle. +The smell of burned rags itches my nose. +New pants lack cuffs and pockets. +The marsh will freeze when cold enough. +They slice the sausage thin with a knife. +The bloom of the rose lasts a few days. +A gray mare walked before the colt. +Breakfast buns are fine with a hot drink. +Bottles hold four kinds of rum. +The man wore a feather in his felt hat. +He wheeled the bike past the winding road. +Drop the ashes on the worn old rug. +The desk and both chairs were painted tan. +Throw out the used paper cup and plate. +A clean neck means a neat collar. +The couch cover and hall drapes were blue. +The stems of the tall glasses cracked and broke. +The wall phone rang loud and often. +The clothes dried on a thin wooden rack. +Turn on the lantern which gives us light. +The cleat sank deeply into the soft turf. +The bills were mailed promptly on the tenth of the month. +To have is better than to wait and hope. +The price is fair for a good antique clock. +The music played on while they talked. +Dispense with a vest on a day like this. +The bunch of grapes was pressed into wine. +He sent the figs, but kept the ripe cherries. +The hinge on the door creaked with old age. +The screen before the fire kept in the sparks. +Fly by night, and you waste little time. +Thick glasses helped him read the print. +Birth and death mark the limits of life. +The chair looked strong but had no bottom. +The kite flew wildly in the high wind. +A fur muff is stylish once more. +The tin box held priceless stones. +We need an end of all such matter. +The case was puzzling to the old and wise. +The bright lanterns were gay on the dark lawn. +We don't get much money but we have fun. +The youth drove with zest, but little skill. +Five years he lived with a shaggy dog. +A fence cuts through the corner lot. +The way to save money is not to spend much. +Shut the hatch before the waves push it in. +The odor of spring makes young hearts jump. +Crack the walnut with your sharp side teeth. +He offered proof in the form of a large chart. +Send the stuff in a thick paper bag. +A quart of milk is water for the most part. +They told wild tales to frighten him. +The three story house was built of stone. +In the rear of the ground floor was a large passage. +A man in a blue sweater sat at the desk. +Oats are a food eaten by horse and man. +Their eyelids droop for want of sleep. +A sip of tea revives his tired friend. +There are many ways to do these things. +Tuck the sheet under the edge of the mat. +A force equal to that would move the earth. +We like to see clear weather. +The work of the tailor is seen on each side. +Take a chance and win a china doll. +Shake the dust from your shoes, stranger. +She was kind to sick old people. +The square wooden crate was packed to be shipped. +The dusty bench stood by the stone wall. +We dress to suit the weather of most days. +Smile when you say nasty words. +A bowl of rice is free with chicken stew. +The water in this well is a source of good health. +Take shelter in this tent, but keep still. +That guy is the writer of a few banned books. +The little tales they tell are false. +The door was barred, locked, and bolted as well. +Ripe pears are fit for a queen's table. +A big wet stain was on the round carpet. +The kite dipped and swayed, but stayed aloft. +The pleasant hours fly by much too soon. +The room was crowded with a wild mob. +This strong arm shall shield your honor. +She blushed when he gave her a white orchid. +The beetle droned in the hot June sun. +Press the pedal with your left foot. +Neat plans fail without luck. +The black trunk fell from the landing. +The bank pressed for payment of the debt. +The theft of the pearl pin was kept secret. +Shake hands with this friendly child. +The vast space stretched into the far distance. +A rich farm is rare in this sandy waste. +His wide grin earned many friends. +Flax makes a fine brand of paper. +Hurdle the pit with the aid of a long pole. +A strong bid may scare your partner stiff. +Even a just cause needs power to win. +Peep under the tent and see the clowns. +The leaf drifts along with a slow spin. +Cheap clothes are flashy but don't last. +A thing of small note can cause despair. +Flood the mails with requests for this book. +A thick coat of black paint covered all. +The pencil was cut to be sharp at both ends. +Those last words were a strong statement. +He wrote his name boldly at the top of the sheet. +Dill pickles are sour but taste fine. +Down that road is the way to the grain farmer. +Either mud or dust are found at all times. +The best method is to fix it in place with clips. +If you mumble your speech will be lost. +At night the alarm roused him from a deep sleep. +Read just what the meter says. +Fill your pack with bright trinkets for the poor. +The small red neon lamp went out. +Clams are small, round, soft, and tasty. +The fan whirled its round blades softly. +The line where the edges join was clean. +Breathe deep and smell the piny air. +It matters not if he reads these words or those. +A brown leather bag hung from its strap. +A toad and a frog are hard to tell apart. +A white silk jacket goes with any shoes. +A break in the dam almost caused a flood. +Paint the sockets in the wall dull green. +The child crawled into the dense grass. +Bribes fail where honest men work. +Trample the spark, else the flames will spread. +The hilt of the sword was carved with fine designs. +A round hole was drilled through the thin board. +Footprints showed the path he took up the beach. +She was waiting at my front lawn. +A vent near the edge brought in fresh air. +Prod the old mule with a crooked stick. +It is a band of steel three inches wide. +The pipe ran almost the length of the ditch. +It was hidden from sight by a mass of leaves and shrubs. +The weight of the package was seen on the high scale. +Wake and rise, and step into the green outdoors. +The green light in the brown box flickered. +The brass tube circled the high wall. +The lobes of her ears were pierced to hold rings. +Hold the hammer near the end to drive the nail. +Next Sunday is the twelfth of the month. +Every word and phrase he speaks is true. +He put his last cartridge into the gun and fired. +They took their kids from the public school. +Drive the screw straight into the wood. +Keep the hatch tight and the watch constant. +Sever the twine with a quick snip of the knife. +Paper will dry out when wet. +Slide the catch back and open the desk. +Help the weak to preserve their strength. +A sullen smile gets few friends. +Stop whistling and watch the boys march. +Jerk the cord, and out tumbles the gold. +Slide the tray across the glass top. +The cloud moved in a stately way and was gone. +Light maple makes for a swell room. +Set the piece here and say nothing. +Dull stories make her laugh. +A stiff cord will do to fasten your shoe. +Get the trust fund to the bank early. +Choose between the high road and the low. +A plea for funds seems to come again. +He lent his coat to the tall gaunt stranger. +There is a strong chance it will happen once more. +The duke left the park in a silver coach. +Greet the new guests and leave quickly. +When the frost has come it is time for turkey. +Sweet words work better than fierce. +A thin stripe runs down the middle. +A six comes up more often than a ten. +Lush fern grow on the lofty rocks. +The ram scared the school children off. +The team with the best timing looks good. +The farmer swapped his horse for a brown ox. +Sit on the perch and tell the others what to do. +A steep trail is painful for our feet. +The early phase of life moves fast. +Green moss grows on the northern side. +Tea in thin china has a sweet taste. +Pitch the straw through the door of the stable. +The latch on the back gate needed a nail. +The goose was brought straight from the old market. +The sink is the thing in which we pile dishes. +A whiff of it will cure the most stubborn cold. +The facts don't always show who is right. +She flaps her cape as she parades the street. +The loss of the cruiser was a blow to the fleet. +Loop the braid to the left and then over. +Plead with the lawyer to drop the lost cause. +Calves thrive on tender spring grass. +Post no bills on this office wall. +Tear a thin sheet from the yellow pad. +A cruise in warm waters in a sleek yacht is fun. +A streak of color ran down the left edge. +It was done before the boy could see it. +Crouch before you jump or miss the mark. +Pack the kits and don't forget the salt. +The square peg will settle in the round hole. +Fine soap saves tender skin. +Poached eggs and tea must suffice. +Bad nerves are jangled by a door slam. +Ship maps are different from those for planes. +Dimes showered down from all sides. +They sang the same tunes at each party. +The sky in the west is tinged with orange red. +The pods of peas ferment in bare fields. +The horse balked and threw the tall rider. +The hitch between the horse and cart broke. +Pile the coal high in the shed corner. +A gold vase is both rare and costly. +The knife was hung inside its bright sheath. +The rarest spice comes from the far East. +The roof should be tilted at a sharp slant. +A smatter of French is worse than none. +The mule trod the treadmill day and night. +The aim of the contest is to raise a great fund. +To send it now in large amounts is bad. +There is a fine hard tang in salty air. +Cod is the main business of the north shore. +The slab was hewn from heavy blocks of slate. +Dunk the stale biscuits into strong drink. +Hang tinsel from both branches. +Cap the jar with a tight brass cover. +The poor boy missed the boat again. +Be sure to set the lamp firmly in the hole. +Pick a card and slip it under the pack. +A round mat will cover the dull spot. +The first part of the plan needs changing. +A good book informs of what we ought to know. +The mail comes in three batches per day. +You cannot brew tea in a cold pot. +Dots of light betrayed the black cat. +Put the chart on the mantel and tack it down. +The night shift men rate extra pay. +The red paper brightened the dim stage. +See the player scoot to third base. +Slide the bill between the two leaves. +Many hands help get the job done. +We don't like to admit our small faults. +No doubt about the way the wind blows. +Dig deep in the earth for pirate's gold. +The steady drip is worse than a drenching rain. +A flat pack takes less luggage space. +Green ice frosted the punch bowl. +A stuffed chair slipped from the moving van. +The stitch will serve but needs to be shortened. +A thin book fits in the side pocket. +The gloss on top made it unfit to read. +The hail pattered on the burnt brown grass. +Seven seals were stamped on great sheets. +Our troops are set to strike heavy blows. +The store was jammed before the sale could start. +It was a bad error on the part of the new judge. +One step more and the board will collapse. +Take the match and strike it against your shoe. +The pot boiled, but the contents failed to jell. +The baby puts his right foot in his mouth. +The bombs left most of the town in ruins. +Stop and stare at the hard working man. +The streets are narrow and full of sharp turns. +The pup jerked the leash as he saw a feline shape. +Open your book to the first page. +Fish evade the net and swim off. +Dip the pail once and let it settle. +Will you please answer that phone. +The big red apple fell to the ground. +The curtain rose and the show was on. +The young prince became heir to the throne. +He sent the boy on a short errand. +Leave now and you will arrive on time. +The corner store was robbed last night. +A gold ring will please most any girl. +The long journey home took a year. +She saw a cat in the neighbor's house. +A pink shell was found on the sandy beach. +Small children came to see him. +The grass and bushes were wet with dew. +The blind man counted his old coins. +A severe storm tore down the barn. +She called his name many times. +When you hear the bell, come quickly. \ No newline at end of file diff --git a/system/TGWUI Extension/languages.json b/system/TGWUI Extension/languages.json new file mode 100644 index 0000000..3895d7f --- /dev/null +++ b/system/TGWUI Extension/languages.json @@ -0,0 +1,18 @@ +{ + "Arabic": "ar", + "Chinese": "zh-cn", + "Czech": "cs", + "Dutch": "nl", + "English": "en", + "French": "fr", + "German": "de", + "Hungarian": "hu", + "Italian": "it", + "Japanese": "ja", + "Korean": "ko", + "Polish": "pl", + "Portuguese": "pt", + "Russian": "ru", + "Spanish": "es", + "Turkish": "tr" +} \ No newline at end of file diff --git a/system/TGWUI Extension/lowvramdisabled.wav b/system/TGWUI Extension/lowvramdisabled.wav new file mode 100644 index 0000000..ac6fef9 Binary files /dev/null and b/system/TGWUI Extension/lowvramdisabled.wav differ diff --git a/system/TGWUI Extension/lowvramenabled.wav b/system/TGWUI Extension/lowvramenabled.wav new file mode 100644 index 0000000..e165bb5 Binary files /dev/null and b/system/TGWUI Extension/lowvramenabled.wav differ diff --git a/system/TGWUI Extension/script.py b/system/TGWUI Extension/script.py new file mode 100644 index 0000000..744798b --- /dev/null +++ b/system/TGWUI Extension/script.py @@ -0,0 +1,692 @@ +import re +import json +import time +import random +import logging +import requests +import threading +import gradio as gr +from pathlib import Path +from modules import chat, shared, ui_chat +from modules.utils import gradio +from requests.exceptions import RequestException, ConnectionError +# Store the current disable level +current_disable_level = logging.getLogger().manager.disable + +#################################################################### +# Load in the confignew.json and put the main settings into params # +#################################################################### +this_dir = Path(__file__).parent.resolve() +def load_config(file_path): + with open(file_path, "r") as config_file: + config = json.load(config_file) + return config +# Set confiig file path +config_file_path = this_dir / "tgwui_remote_config.json" +# Load the params dictionary from the tgwui_remote_config.json file +params = load_config(config_file_path) +# Set branding +branding = params['branding'] + +################################ +# Print Spashscreen to console # +################################ +print(f"[{branding}Startup]\033[94m _ _ _ \033[1;35m_____ _ _ \033[0m _____ _____ ____ ") +print(f"[{branding}Startup]\033[94m / \ | | |\033[1;35m_ _|_ _| | | __ \033[0m |_ _|_ _/ ___| ") +print(f"[{branding}Startup]\033[94m / _ \ | | |\033[1;35m | |/ _` | | |/ / \033[0m | | | | \___ \ ") +print(f"[{branding}Startup]\033[94m / ___ \| | |\033[1;35m | | (_| | | < \033[0m | | | | ___) |") +print(f"[{branding}Startup]\033[94m /_/ \_\_|_|\033[1;35m |_|\__,_|_|_|\_\ \033[0m |_| |_| |____/ ") +print(f"[{branding}Startup]") +print(f"[{branding}Startup] \033[92m{branding}startup Mode : \033[93mText-Gen-webui Remote\033[0m") +print(f"[{branding}Startup]") + +########################## +# Setup global variables # +########################## +# STARTUP VARIABLE - Import languges file for Gradio to be able to display them in the interface +with open(this_dir / "languages.json", encoding="utf8") as f: + languages = json.load(f) +# Create a global lock for tracking TTS generation occuring +process_lock = threading.Lock() +# Pull the values for IP address, port and protocol for communication with the AllTalk renote server +alltalk_protocol = params["api_def"]["api_alltalk_protocol"] +alltalk_ip_port = params["api_def"]["api_alltalk_ip_port"] +# Pull the connection timeout value for communication requests with the AllTalk remote server +connection_timeout = params["api_def"]["api_connection_timeout"] +# Create a few base global variables that are required +models_available = None # Gets populated with the list of all models/engines available on the AllTalk Server +alltalk_settings = None # Gets populated with the list of all settings currently set on the AllTalk Server +current_model_loaded = None # Gets populated with the name of the current loaded TTS engine/model on the AllTalk Server +# Used to detect if a model is loaded in to AllTalk server to block TTS genereation if needed. +tts_model_loaded = None + +################################################# +# Pull all the settings from the AllTalk Server # +################################################# +def get_alltalk_settings(): + global current_model_loaded, models_available + voices_url = f"{alltalk_protocol}{alltalk_ip_port}/api/voices" + settings_url = f"{alltalk_protocol}{alltalk_ip_port}/api/currentsettings" + rvcvoices_url = f"{alltalk_protocol}{alltalk_ip_port}/api/rvcvoices" + + try: + voices_response = requests.get(voices_url, timeout=connection_timeout) + rvcvoices_response = requests.get(rvcvoices_url, timeout=connection_timeout) + settings_response = requests.get(settings_url, timeout=connection_timeout) + + if voices_response.status_code == 200 and settings_response.status_code == 200: + voices_data = voices_response.json() + rvcvoices_data = rvcvoices_response.json() + settings_data = settings_response.json() + + models_available = [model["name"] for model in settings_data["models_available"]] + current_model_loaded = settings_data["current_model_loaded"] + + return { + "voices": sorted(voices_data["voices"]), + "rvcvoices": (rvcvoices_data["rvcvoices"]), + "models_available": models_available, + "current_model_loaded": current_model_loaded, + "manufacturer_name": settings_data.get("manufacturer_name", ""), + "deepspeed_capable": settings_data.get("deepspeed_capable", False), + "deepspeed_available": settings_data.get("deepspeed_available", False), + "deepspeed_enabled": settings_data.get("deepspeed_enabled", False), + "generationspeed_capable": settings_data.get("generationspeed_capable", False), + "generationspeed_set": settings_data.get("generationspeed_set", 1.0), + "lowvram_capable": settings_data.get("lowvram_capable", False), + "lowvram_enabled": settings_data.get("lowvram_enabled", False), + "pitch_capable": settings_data.get("pitch_capable", False), + "pitch_set": settings_data.get("pitch_enabled", False), + "repetitionpenalty_capable": settings_data.get("repetitionpenalty_capable", False), + "repetitionpenalty_set": settings_data.get("repetitionpenalty_set", 10.0), + "streaming_capable": settings_data.get("streaming_capable", False), + "temperature_capable": settings_data.get("temperature_capable", False), + "temperature_set": settings_data.get("temperature_set", 0.75), + "ttsengines_installed": settings_data.get("ttsengines_installed", False), + "languages_capable": settings_data.get("languages_capable", False), + "multivoice_capable": settings_data.get("multivoice_capable", False), + "multimodel_capable": settings_data.get("multimodel_capable", False), + } + else: + print(f"[{branding}Server] \033[91mWarning\033[0m Failed to retrieve {branding}settings from API.") + if voices_response.status_code != 200: + print(f"[{branding}Server] \033[91mWarning\033[0m Failed to retrieve voices from API. Status code:\n{voices_response.status_code}") + if settings_response.status_code != 200: + print(f"[{branding}Server] \033[91mWarning\033[0m Failed to retrieve current settings from API. Status code:\n{settings_response.status_code}") + return { + "voices": ["Please Refresh Settings"], + "rvcvoices": ["Please Refresh Settings"], + "models_available": ["Please Refresh Settings"], + "current_model_loaded": "Please Refresh Settings", + "manufacturer_name": "", + "deepspeed_capable": False, + "deepspeed_available": False, + "deepspeed_enabled": False, + "generationspeed_capable": False, + "generationspeed_set": 1.0, + "lowvram_capable": False, + "lowvram_enabled": False, + "pitch_capable": False, + "pitch_set": 0, + "repetitionpenalty_capable": False, + "repetitionpenalty_set": 10.0, + "streaming_capable": False, + "temperature_capable": False, + "temperature_set": 0.75, + "ttsengines_installed": False, + "languages_capable": False, + "multivoice_capable": False, + "multimodel_capable": False, + } + except (RequestException, ConnectionError) as e: + print(f"[{branding}Server] \033[91mWarning\033[0m Unable to connect to the {branding}server. Status code:\n{str(e)}") + return { + "voices": ["Please Refresh Settings"], + "rvcvoices": ["Please Refresh Settings"], + "models_available": ["Please Refresh Settings"], + "current_model_loaded": "Please Refresh Settings", + "manufacturer_name": "", + "deepspeed_capable": False, + "deepspeed_available": False, + "deepspeed_enabled": False, + "generationspeed_capable": False, + "generationspeed_set": 1.0, + "lowvram_capable": False, + "lowvram_enabled": False, + "pitch_capable": False, + "pitch_set": 0, + "repetitionpenalty_capable": False, + "repetitionpenalty_set": 10.0, + "streaming_capable": False, + "temperature_capable": False, + "temperature_set": 0.75, + "ttsengines_installed": False, + "languages_capable": False, + "multivoice_capable": False, + "multimodel_capable": False, + } + +############################# +#### TTS STOP GENERATION #### +############################# +def stop_generate_tts(): + api_url = f"{alltalk_protocol}{alltalk_ip_port}/api/stop-generation" + try: + response = requests.put(api_url, timeout=connection_timeout) + if response.status_code == 200: + return response.json()["message"] + else: + print(f"[{branding}Server] \033[91mWarning\033[0m Failed to stop generation. Status code:\n{response.status_code}") + return {"message": "Failed to stop generation"} + except (RequestException, ConnectionError) as e: + print(f"[{branding}Server] \033[91mWarning\033[0m Unable to connect to the {branding}server. Status code:\n{str(e)}") + return {"message": "Failed to stop generation"} + +##################################### +#### MODEL LOADING AND UNLOADING #### +##################################### +# MODEL - Swap model based on Gradio selection +def send_reload_request(value_sent): + try: + url = f"{alltalk_protocol}{alltalk_ip_port}/api/reload" + payload = {"tts_method": value_sent} + response = requests.post(url, params=payload) + response.raise_for_status() # Raises an HTTPError for bad responses + except requests.exceptions.RequestException as e: + # Handle the HTTP request error + print(f"[{branding}Server] \033[91mWarning\033[0m Error during request to webserver process: Status code:\n{e}") + return {"status": "error", "message": str(e)} + +#################################################################################################### +# TGWUI # Saves all settings back to the config file for the REMOTE version of the TGWUI extension # +#################################################################################################### +def tgwui_save_settings(): + settings = { + "branding": params['branding'], + "tgwui": { + "tgwui_activate_tts": params["tgwui"]['tgwui_activate_tts'], + "tgwui_autoplay_tts": params["tgwui"]['tgwui_autoplay_tts'], + "tgwui_narrator_enabled": params["tgwui"]['tgwui_narrator_enabled'], + "tgwui_non_quoted_text_is": params["tgwui"]["tgwui_non_quoted_text_is"], + "tgwui_deepspeed_enabled": params["tgwui"]['tgwui_deepspeed_enabled'], + "tgwui_language": params["tgwui"]['tgwui_language'], + "tgwui_lowvram_enabled": params["tgwui"]['tgwui_lowvram_enabled'], + "tgwui_pitch_set": params["tgwui"]['tgwui_pitch_set'], + "tgwui_temperature_set": params["tgwui"]['tgwui_temperature_set'], + "tgwui_repetitionpenalty_set": params["tgwui"]['tgwui_repetitionpenalty_set'], + "tgwui_generationspeed_set": params["tgwui"]['tgwui_generationspeed_set'], + "tgwui_narrator_voice": params["tgwui"]['tgwui_narrator_voice'], + "tgwui_show_text": params["tgwui"]['tgwui_show_text'], + "tgwui_character_voice": params["tgwui"]['tgwui_character_voice'], + "tgwui_rvc_char_voice": params["tgwui"]['tgwui_rvc_char_voice'], + "tgwui_rvc_narr_voice": params["tgwui"]['tgwui_rvc_narr_voice'] + }, + "api_def": { + "api_use_legacy_api": params['api_def']['api_use_legacy_api'], + "api_alltalk_protocol": alltalk_protocol, + "api_alltalk_ip_port": alltalk_ip_port, + "api_connection_timeout": 5, + } + } + + with open(config_file_path, "w") as file: + json.dump(settings, file, indent=4) + +############################################# +# Low VRAM change request to enable/disable # +############################################# +# LOW VRAM - Gradio Checkbox handling +def send_lowvram_request(value_sent): + try: + params["tgwui"]["tts_model_loaded"] = False + if value_sent: + audio_path = this_dir / "lowvramenabled.wav" + else: + audio_path = this_dir / "lowvramdisabled.wav" + url = f"{alltalk_protocol}{alltalk_ip_port}/api/lowvramsetting?new_low_vram_value={value_sent}" + headers = {"Content-Type": "application/json"} + response = requests.post(url, headers=headers) + response.raise_for_status() # Raises an HTTPError for bad responses + json_response = response.json() + # Check if the low VRAM request was successful + if json_response.get("status") == "lowvram-success": + # Update any relevant variables or perform other actions on success + params["tgwui"]["tts_model_loaded"] = True + return f'' + except requests.exceptions.RequestException as e: + # Handle the HTTP request error + print(f"[{branding}Server] \033[91mWarning\033[0m Error during request to webserver process: Status code:\n{e}") + return {"status": "error", "message": str(e)} + +################### +#### DeepSpeed #### +################### +def send_deepspeed_request(value_sent): + try: + params["tgwui"]["tts_model_loaded"] = False + if value_sent: + audio_path = this_dir / "deepspeedenabled.wav" + else: + audio_path = this_dir / "deepspeeddisabled.wav" + url = f"{alltalk_protocol}{alltalk_ip_port}/api/deepspeed?new_deepspeed_value={value_sent}" + headers = {"Content-Type": "application/json"} + response = requests.post(url, headers=headers) + response.raise_for_status() # Raises an HTTPError for bad responses + json_response = response.json() + # Check if the deepspeed request was successful + if json_response.get("status") == "deepspeed-success": + # Update any relevant variables or perform other actions on success + params["tgwui"]["tts_model_loaded"] = True + return f'' + except requests.exceptions.RequestException as e: + # Handle the HTTP request error + print(f"[{branding}Server] \033[91mWarning\033[0m Error during request to webserver process: Status code:\n{e}") + return {"status": "error", "message": str(e)} + +################################# +#### TTS STANDARD GENERATION #### +################################# +def send_and_generate(gen_text, gen_character_voice, gen_rvccharacter_voice, gen_narrator_voice, gen_rvcnarrator_voice, gen_narrator_activated, gen_textnotinisde, gen_repetition, gen_language, gen_filter, gen_speed, gen_pitch, gen_autoplay, gen_autoplay_vol, gen_file_name, gen_temperature, gen_filetimestamp, gen_stream, gen_stopcurrentgen): + api_url = f"{alltalk_protocol}{alltalk_ip_port}/api/tts-generate" + if gen_stopcurrentgen: + stop_generate_tts() # Call for the current generation to stop + tgwui_save_settings() # Save the current TGWUI settings to the config file + if gen_stream == "true": + api_url = f"{alltalk_protocol}{alltalk_ip_port}/api/tts-generate-streaming" + encoded_text = requests.utils.quote(gen_text) + streaming_url = f"{api_url}?text={encoded_text}&voice={gen_character_voice}&language={gen_language}&output_file={gen_file_name}" + return streaming_url, str("TTS Streaming Audio Generated") + else: + data = { + "text_input": gen_text, + "text_filtering": gen_filter, + "character_voice_gen": gen_character_voice, + "rvccharacter_voice_gen": gen_rvccharacter_voice, + "narrator_enabled": str(gen_narrator_activated).lower(), + "narrator_voice_gen": gen_narrator_voice, + "rvcnarrator_voice_gen": gen_rvcnarrator_voice, + "text_not_inside": gen_textnotinisde, + "language": gen_language, + "output_file_name": str(gen_file_name), + "output_file_timestamp": str(gen_filetimestamp).lower(), + "autoplay": str(gen_autoplay).lower(), + "autoplay_volume": str(gen_autoplay_vol), + "speed": str(gen_speed), + "pitch": str(gen_pitch), + "temperature": str(gen_temperature), + "repetition_penalty": str(gen_repetition), + } + #print(f"Debug: Generate request param:", data) if params["debug_tts"] else None + try: + response = requests.post(api_url, data=data) + response.raise_for_status() # Raise an exception for 4xx or 5xx status codes + result = response.json() + if gen_autoplay == "true": + return None, str("TTS Audio Generated (Played remotely)") + else: + if params["api_def"]["api_use_legacy_api"]: + return result['output_file_url'], str("TTS Audio Generated") + else: + # Prepend the URL and PORT to the output_file_url + output_file_url = f"{alltalk_protocol}{alltalk_ip_port}{result['output_file_url']}" + return output_file_url, str("TTS Audio Generated") + except (RequestException, ConnectionError) as e: + print(f"[{branding}Server] \033[91mWarning\033[0m Error occurred during the API request: Status code:\n{str(e)}") + return None, str("Error occurred during the API request") + +#################################################################### +# TGWUI # Text-generation-webui sends text for TTS generation here # +#################################################################### +def output_modifier(string, state): + if not params["tgwui"]["tgwui_activate_tts"]: + return string + # Strip out Images + img_info = "" + cleaned_text, img_info = tgwui_extract_and_remove_images(string) + if cleaned_text is None: + return + # Get current settings + language_code = languages.get(params["tgwui"]["tgwui_language"]) + character_voice = params["tgwui"]["tgwui_character_voice"] + rvc_character_voice = params["tgwui"]["tgwui_rvc_char_voice"] + narrator_voice = params["tgwui"]["tgwui_narrator_voice"] + rvc_narrator_voice = params["tgwui"]["tgwui_rvc_narr_voice"] + narrator_enabled = params["tgwui"]["tgwui_narrator_enabled"] + text_not_inside = params["tgwui"]["tgwui_non_quoted_text_is"] + repetition_policy = params["tgwui"]["tgwui_repetitionpenalty_set"] + text_filtering = "html" + speed = params["tgwui"]["tgwui_generationspeed_set"] + pitch = params["tgwui"]["tgwui_pitch_set"] + autoplay = False # This is never used in TGWUI so a base False setting is sent + autoplay_volume = 0.8 # This is never used in TGWUI so a base False setting is sent + temperature = params["tgwui"]["tgwui_temperature_set"] + # Lock and process TTS request + if process_lock.acquire(blocking=False): + try: + if "character_menu" in state: + output_file = state["character_menu"] + else: + output_file = str("TTSOUT_") + generate_response, status_message = send_and_generate(cleaned_text, character_voice, rvc_character_voice, narrator_voice, rvc_narrator_voice, narrator_enabled, text_not_inside, repetition_policy, language_code, text_filtering, speed, pitch, autoplay, autoplay_volume, output_file, temperature, True, False, False) + if status_message == "TTS Audio Generated": + # Handle Gradio and playback + autoplay = "autoplay" if params["tgwui"]["tgwui_autoplay_tts"] else "" + string = f'' + if params["tgwui"]["tgwui_show_text"]: + string += tgwui_reinsert_images(cleaned_text, img_info) + shared.processing_message = "*Is typing...*" + return string + else: + print(f"[{branding}Server] \033[91mWarning\033[0m Audio generation failed. Status code:\n{status_message}") + finally: + # Always release the lock, whether an exception occurs or not + process_lock.release() + else: + # The lock is already acquired + print(f"[{branding}Model] \033[91mWarning\033[0m Audio generation is already in progress. Please wait.") + return + +########################################################################################################### +# TGWUI # Strips out images from the TGWUI string if needed and re-inserts the image after TTS generation # +########################################################################################################### +img_pattern = r']*src\s*=\s*["\'][^"\'>]+["\'][^>]*>' + +def tgwui_extract_and_remove_images(text): + """ + Extracts all image data from the text and removes it for clean TTS processing. + Returns the cleaned text and the extracted image data. + """ + img_matches = re.findall(img_pattern, text) + img_info = "\n".join(img_matches) # Store extracted image data + cleaned_text = re.sub(img_pattern, '', text) # Remove images from text + return cleaned_text, img_info + +def tgwui_reinsert_images(text, img_info): + """ + Reinserts the previously extracted image data back into the text. + """ + if img_info: # Check if there are images to reinsert + text += f"\n\n{img_info}" + return text + +################################################################ +# TGWUI # Used to generate a preview voice sample within TGWUI # +################################################################ +def random_sentence(): + with open(this_dir / "harvard_sentences.txt") as f: + return random.choice(list(f)).rstrip() + +def voice_preview(string): + if not params["tgwui"]["tgwui_activate_tts"]: + return string + language_code = languages.get(params["tgwui"]["tgwui_language"]) + if not string: + string = random_sentence() + generate_response, status_message = send_and_generate(string, params["tgwui"]["tgwui_character_voice"], params["tgwui"]["tgwui_rvc_char_voice"], params["tgwui"]["tgwui_narrator_voice"], params["tgwui"]["tgwui_rvc_narr_voice"], False, "character", params["tgwui"]["tgwui_repetitionpenalty_set"], language_code, "standard", params["tgwui"]["tgwui_generationspeed_set"], params["tgwui"]["tgwui_pitch_set"], False, 0.8, "previewvoice", params["tgwui"]["tgwui_temperature_set"], False, False, False) + if status_message == "TTS Audio Generated": + # Handle Gradio and playback + autoplay = "autoplay" if params["tgwui"]["tgwui_autoplay_tts"] else "" + return f'' + else: + # Handle the case where audio generation was not successful + return f"[{branding}Server] Audio generation failed. Status code:\n{status_message}" + +################################################################### +# TGWUI # Used to inform TGWUI that TTS is disabled/not activated # +################################################################### +def state_modifier(state): + if not params["tgwui"]["tgwui_activate_tts"]: + return state + state["stream"] = False + return state + +################################################################### +# TGWUI # Sends message to TGWUI interface during TTS generation # +################################################################### +def input_modifier(string, state): + if not params["tgwui"]["tgwui_activate_tts"]: + return string + shared.processing_message = "*Is recording a voice message...*" + return string + +######################################################################## +# TGWUI # Used to delete historic TTS audios from TGWUI chat interface # +######################################################################## +def remove_tts_from_history(history): + for i, entry in enumerate(history["internal"]): + history["visible"][i] = [history["visible"][i][0], entry[1]] + return history + +def toggle_text_in_history(history): + for i, entry in enumerate(history["visible"]): + visible_reply = entry[1] + if visible_reply.startswith("')[0]}\n\n{reply}", + ] + else: + history["visible"][i] = [ + history["visible"][i][0], + f"{visible_reply.split('')[0]}", + ] + return history + +def history_modifier(history): + # Remove autoplay from the last reply + if len(history["internal"]) > 0: + history["visible"][-1] = [ + history["visible"][-1][0], + history["visible"][-1][1].replace("controls autoplay>", "controls>"), + ] + return history + +########################################################### +# TGWUI # Update IP address and Port from TGWUI interface # +########################################################### +def tgwui_update_alltalk_ip_port(value_sent): + global alltalk_ip_port + alltalk_ip_port = value_sent + +########################################################### +# TGWUI # Update http/https protocol from TGWUI interface # +########################################################### +# Update Protocol from Gradio +def tgwui_update_alltalk_protocol(value_sent): + global alltalk_protocol + alltalk_protocol = value_sent + +########################################################################################################################### +# TGWUI # Debounces model updates when tgwui_update_dropdowns is called, preventing an update from causing a model reload # +########################################################################################################################### +def tgwui_handle_ttsmodel_dropdown_change(model_name): + if not getattr(tgwui_handle_ttsmodel_dropdown_change, "skip_reload", False): + send_reload_request(model_name) + tgwui_handle_ttsmodel_dropdown_change.skip_reload = False + +################################################################################################## +# TGWUI # Pulls the current AllTalk Server settings & updates gradio when Refresh button pressed # +################################################################################################## +def tgwui_update_dropdowns(): + global at_settings + at_settings = get_alltalk_settings() # Pull all the current settings from the AllTalk server, if its online. + current_voices = at_settings["voices"] + rvccurrent_voices = at_settings["rvcvoices"] + rvccurrent_character_voice = params["tgwui"]["tgwui_rvc_char_voice"] + rvccurrent_narrator_voice = params["tgwui"]["tgwui_rvc_char_voice"] + current_models_available = sorted(at_settings["models_available"]) + current_model_loaded = at_settings["current_model_loaded"] + current_character_voice = params["tgwui"]["tgwui_character_voice"] + current_narrator_voice = params["tgwui"]["tgwui_narrator_voice"] + current_lowvram_capable = at_settings["lowvram_capable"] + current_lowvram_enabled = at_settings["lowvram_enabled"] + current_temperature_capable = at_settings["temperature_capable"] + current_repetitionpenalty_capable = at_settings["repetitionpenalty_capable"] + current_generationspeed_capable = at_settings["generationspeed_capable"] + current_pitch_capable = at_settings["pitch_capable"] + current_deepspeed_capable = at_settings["deepspeed_capable"] + current_deepspeed_enabled = at_settings["deepspeed_enabled"] + current_non_quoted_text_is = params.get("tgwui")["tgwui_non_quoted_text_is"] # Use the correct parameter path + current_languages_capable = at_settings["languages_capable"] + if at_settings["languages_capable"]: + language_label = "Languages" + else: + language_label = "Model not multi language" + if current_character_voice not in current_voices: + current_character_voice = current_voices[0] if current_voices else "" + if current_narrator_voice not in current_voices: + current_narrator_voice = current_voices[0] if current_voices else "" + if rvccurrent_character_voice not in rvccurrent_voices: + rvccurrent_character_voice = rvccurrent_voices[0] if rvccurrent_voices else "" + if rvccurrent_narrator_voice not in rvccurrent_voices: + rvccurrent_narrator_voice = rvccurrent_voices[0] if rvccurrent_voices else "" + tgwui_handle_ttsmodel_dropdown_change.skip_reload = True # Debounce tgwui_tts_dropdown_gr and stop it sending a model reload when it is updated. + + return ( + gr.Checkbox(interactive=current_lowvram_capable, value=current_lowvram_enabled), + gr.Checkbox(interactive=current_deepspeed_capable, value=current_deepspeed_enabled), + gr.Dropdown(choices=current_voices, value=current_character_voice), + gr.Dropdown(choices=rvccurrent_voices, value=rvccurrent_character_voice, interactive=True), + gr.Dropdown(choices=current_voices, value=current_narrator_voice), + gr.Dropdown(choices=rvccurrent_voices, value=rvccurrent_narrator_voice, interactive=True), + gr.Dropdown(choices=current_models_available, value=current_model_loaded), + gr.Dropdown(interactive=current_temperature_capable), + gr.Dropdown(interactive=current_repetitionpenalty_capable), + gr.Dropdown(interactive=current_languages_capable, label=language_label), + gr.Dropdown(interactive=current_generationspeed_capable), + gr.Dropdown(interactive=current_pitch_capable), + gr.Dropdown(value=current_non_quoted_text_is), + ) + +############################################################### +# TGWUI # Gradio interface & layout for Text-generation-webui # +############################################################### +def ui(): + global alltalk_settings + alltalk_settings = get_alltalk_settings() # Pull all the current settings from the AllTalk server, if its online. + with gr.Accordion(params["branding"] + " TTS (Text-gen-webui Remote)"): + tgwui_available_voices_gr = alltalk_settings["voices"] + tgwui_rvc_available_voices_gr = alltalk_settings["rvcvoices"] + # Activate alltalk_tts, Enable autoplay, Show text + with gr.Row(): + tgwui_activate_tts_gr = gr.Checkbox(value=params["tgwui"]["tgwui_activate_tts"], label="Enable TGWUI TTS") + tgwui_autoplay_gr = gr.Checkbox(value=params["tgwui"]["tgwui_autoplay_tts"], label="Autoplay TTS Generated") + tgwui_show_text_gr = gr.Checkbox(value=params["tgwui"]["tgwui_show_text"], label="Show Text in chat") + + # Low vram enable, Deepspeed enable, Link + with gr.Row(): + tgwui_lowvram_enabled_gr = gr.Checkbox(value=alltalk_settings["lowvram_enabled"] if alltalk_settings["lowvram_capable"] else False, label="Enable Low VRAM Mode", interactive=alltalk_settings["lowvram_capable"]) + tgwui_lowvram_enabled_play_gr = gr.HTML(visible=False) + tgwui_deepspeed_enabled_gr = gr.Checkbox(value=params["tgwui"]["tgwui_deepspeed_enabled"], label="Enable DeepSpeed", interactive=alltalk_settings["deepspeed_capable"],) + tgwui_deepspeed_enabled_play_gr = gr.HTML(visible=False) + tgwui_empty_space_gr = gr.HTML(f"

AllTalk Server & Documentation Link") + + # Model, Language, Character voice + with gr.Row(): + tgwui_tts_dropdown_gr = gr.Dropdown(choices=models_available, label="TTS Engine/Model", value=current_model_loaded,) + tgwui_language_gr = gr.Dropdown(languages.keys(), label="Languages" if alltalk_settings["languages_capable"] else "Model not multi language", interactive=alltalk_settings["languages_capable"], value=params["tgwui"]["tgwui_language"]) + tgwui_narrator_enabled_gr = gr.Dropdown(choices=[("Enabled", "true"), ("Disabled", "false"), ("Silent", "silent")], label="Narrator Enable", value="true" if params.get("tgwui_narrator_enabled") == "true" else ("silent" if params.get("tgwui_narrator_enabled") == "silent" else "false")) + + # Narrator + with gr.Row(): + tgwui_available_voices_gr = alltalk_settings["voices"] + tgwui_default_voice_gr = params["tgwui"]["tgwui_character_voice"] + if tgwui_default_voice_gr not in tgwui_available_voices_gr: + tgwui_default_voice_gr = tgwui_available_voices_gr[0] if tgwui_available_voices_gr else "" + tgwui_character_voice_gr = gr.Dropdown(choices=tgwui_available_voices_gr, label="Character Voice", value=tgwui_default_voice_gr, allow_custom_value=True,) + tgwui_narr_voice_gr = params["tgwui"]["tgwui_narrator_voice"] + if tgwui_narr_voice_gr not in tgwui_available_voices_gr: + tgwui_narr_voice_gr = tgwui_available_voices_gr[0] if tgwui_available_voices_gr else "" + tgwui_narrator_voice_gr = gr.Dropdown(choices=tgwui_available_voices_gr, label="Narrator Voice", value=tgwui_narr_voice_gr, allow_custom_value=True,) + tgwui_non_quoted_text_is_gr = gr.Dropdown(choices=[("Character", "character"), ("Narrator", "narrator"), ("Silent", "silent")], label='Narrator unmarked text is', value=params.get("tgwui_non_quoted_text_is", "character")) + + # RVC voices + with gr.Row(): + tgwui_rvc_default_voice_gr = params["tgwui"]["tgwui_rvc_char_voice"] + tgwui_rvc_narrator_voice_gr = params["tgwui"]["tgwui_rvc_narr_voice"] + if tgwui_rvc_default_voice_gr not in tgwui_rvc_available_voices_gr: + tgwui_rvc_default_voice_gr = tgwui_rvc_available_voices_gr[0] if tgwui_rvc_available_voices_gr else "" + tgwui_rvc_char_voice_gr = gr.Dropdown(choices=tgwui_rvc_available_voices_gr, label="RVC Character Voice", value=tgwui_rvc_default_voice_gr, allow_custom_value=True,) + if tgwui_rvc_narrator_voice_gr not in tgwui_rvc_available_voices_gr: + tgwui_rvc_narrator_voice_gr = tgwui_rvc_available_voices_gr[0] if tgwui_rvc_available_voices_gr else "" + tgwui_rvc_narr_voice_gr = gr.Dropdown(choices=tgwui_rvc_available_voices_gr, label="RVC Narrator Voice", value=tgwui_rvc_narrator_voice_gr, allow_custom_value=True,) + + # Temperature, Repetition Penalty, Speed, pitch + with gr.Row(): + tgwui_temperature_set_gr = gr.Slider(minimum=0.05, maximum=1, step=0.05, label="Temperature", value=params["tgwui"]["tgwui_temperature_set"], interactive=alltalk_settings["temperature_capable"]) + tgwui_repetitionpenalty_set_gr = gr.Slider(minimum=0.5, maximum=20, step=0.5, label="Repetition Penalty", value=params["tgwui"]["tgwui_repetitionpenalty_set"], interactive=alltalk_settings["repetitionpenalty_capable"]) + with gr.Row(): + tgwui_generationspeed_set_gr = gr.Slider(minimum=0.30, maximum=2.00, step=0.10, label="TTS Speed", value=params["tgwui"]["tgwui_generationspeed_set"], interactive=alltalk_settings["generationspeed_capable"]) + tgwui_pitch_set_gr = gr.Slider(minimum=-10, maximum=10, step=1, label="Pitch", value=params["tgwui"]["tgwui_pitch_set"], interactive=alltalk_settings["pitch_capable"]) + + # Preview speech + with gr.Row(): + tgwui_preview_text_gr = gr.Text(show_label=False, placeholder="Preview text", elem_id="silero_preview_text", scale=2,) + tgwui_preview_play_gr = gr.Button("Generate Preview", scale=1) + tgwui_preview_audio_gr = gr.HTML(visible=False) + + with gr.Row(): + tgwui_protocol_gr = gr.Dropdown(choices=["http://", "https://"], label="AllTalk Server Protocol", value=alltalk_protocol) + tgwui_protocol_gr.change(tgwui_update_alltalk_protocol, tgwui_protocol_gr, None) + tgwui_ip_address_port_gr = gr.Textbox(label="AllTalk Server IP:Port", value=alltalk_ip_port) + tgwui_ip_address_port_gr.change(tgwui_update_alltalk_ip_port, tgwui_ip_address_port_gr, None) + tgwui_refresh_settings_gr = gr.Button("Refresh settings & voices") + tgwui_refresh_settings_gr.click(tgwui_update_dropdowns, None, [tgwui_lowvram_enabled_gr, tgwui_deepspeed_enabled_gr, tgwui_character_voice_gr, tgwui_rvc_char_voice_gr, tgwui_narrator_voice_gr, tgwui_rvc_narr_voice_gr, tgwui_tts_dropdown_gr, tgwui_temperature_set_gr, tgwui_repetitionpenalty_set_gr, tgwui_language_gr, tgwui_generationspeed_set_gr, tgwui_pitch_set_gr, tgwui_non_quoted_text_is_gr]) + + with gr.Row(): + tgwui_convert_gr = gr.Button("Remove old TTS audio and leave only message texts") + tgwui_convert_cancel_gr = gr.Button("Cancel", visible=False) + tgwui_convert_confirm_gr = gr.Button("Confirm (cannot be undone)", variant="stop", visible=False) + tgwui_stop_generation_gr = gr.Button("Stop current TTS generation") + tgwui_stop_generation_gr.click(stop_generate_tts, None, None,) + + # Convert history with confirmation + convert_arr = [tgwui_convert_confirm_gr, tgwui_convert_gr, tgwui_convert_cancel_gr] + tgwui_convert_gr.click(lambda: [gr.update(visible=True), gr.update(visible=False), gr.update(visible=True),], None, convert_arr,) + tgwui_convert_confirm_gr.click(lambda: [ gr.update(visible=False), gr.update(visible=True), gr.update(visible=False),], None, convert_arr, + ).then(remove_tts_from_history, gradio("history"), gradio("history") + ).then(chat.save_history, gradio("history", "unique_id", "character_menu", "mode"), None, + ).then(chat.redraw_html, gradio(ui_chat.reload_arr), gradio("display")) + tgwui_convert_cancel_gr.click(lambda: [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False),], None, convert_arr,) + + # Toggle message text in history + tgwui_show_text_gr.change(lambda x: params["tgwui"].update({"tgwui_show_text": x}), tgwui_show_text_gr, None + ).then(toggle_text_in_history, gradio("history"), gradio("history") + ).then(chat.save_history, gradio("history", "unique_id", "character_menu", "mode"), None, + ).then(chat.redraw_html, gradio(ui_chat.reload_arr), gradio("display")) + + # Event functions to update the parameters in the backend + tgwui_activate_tts_gr.change(lambda x: params["tgwui"].update({"tgwui_activate_tts": x}), tgwui_activate_tts_gr, None) + tgwui_autoplay_gr.change(lambda x: params["tgwui"].update({"tgwui_autoplay_tts": x}), tgwui_autoplay_gr, None) + tgwui_lowvram_enabled_gr.change(lambda x: params["tgwui"].update({"tgwui_lowvram_enabled": x}), tgwui_lowvram_enabled_gr, None) + tgwui_lowvram_enabled_gr.change(lambda x: send_lowvram_request(x), tgwui_lowvram_enabled_gr, tgwui_lowvram_enabled_play_gr, None) + + # Trigger the send_reload_request function when the dropdown value changes + tgwui_tts_dropdown_gr.change(tgwui_handle_ttsmodel_dropdown_change, tgwui_tts_dropdown_gr, None) + + tgwui_deepspeed_enabled_gr.change(lambda x: params["tgwui"].update({"tgwui_deepspeed_enabled": x}), tgwui_deepspeed_enabled_gr, None) + tgwui_deepspeed_enabled_gr.change(send_deepspeed_request, tgwui_deepspeed_enabled_gr, tgwui_deepspeed_enabled_play_gr, None) + tgwui_character_voice_gr.change(lambda x: params["tgwui"].update({"tgwui_character_voice": x}), tgwui_character_voice_gr, None) + tgwui_language_gr.change(lambda x: params["tgwui"].update({"tgwui_language": x}), tgwui_language_gr, None) + + # TSS Settings + tgwui_temperature_set_gr.change(lambda x: params["tgwui"].update({"tgwui_temperature_set": x}), tgwui_temperature_set_gr, None) + tgwui_repetitionpenalty_set_gr.change(lambda x: params["tgwui"].update({"tgwui_repetitionpenalty_set": x}), tgwui_repetitionpenalty_set_gr, None) + tgwui_generationspeed_set_gr.change(lambda x: params["tgwui"].update({"tgwui_generationspeed_set": x}), tgwui_generationspeed_set_gr, None) + tgwui_pitch_set_gr.change(lambda x: params["tgwui"].update({"tgwui_pitch_set": x}), tgwui_pitch_set_gr, None) + + # Narrator selection actions + tgwui_narrator_enabled_gr.change(lambda x: params["tgwui"].update({"tgwui_narrator_enabled": x}), tgwui_narrator_enabled_gr, None) + tgwui_non_quoted_text_is_gr.change(lambda x: params["tgwui"].update({"tgwui_non_quoted_text_is": x}), tgwui_non_quoted_text_is_gr, None) + tgwui_narrator_voice_gr.change(lambda x: params["tgwui"].update({"tgwui_narrator_voice": x}), tgwui_narrator_voice_gr, None) + + # RVC selection actions + tgwui_rvc_char_voice_gr.change(lambda x: params["tgwui"].update({"tgwui_rvc_char_voice": x}), tgwui_rvc_char_voice_gr, None) + tgwui_rvc_narr_voice_gr.change(lambda x: params["tgwui"].update({"tgwui_rvc_narr_voice": x}), tgwui_rvc_narr_voice_gr, None) + + # Play preview + tgwui_preview_text_gr.submit(voice_preview, tgwui_preview_text_gr, tgwui_preview_audio_gr) + tgwui_preview_play_gr.click(voice_preview, tgwui_preview_text_gr, tgwui_preview_audio_gr) + diff --git a/system/TGWUI Extension/tgwui_remote_config.json b/system/TGWUI Extension/tgwui_remote_config.json new file mode 100644 index 0000000..e27cbbc --- /dev/null +++ b/system/TGWUI Extension/tgwui_remote_config.json @@ -0,0 +1,27 @@ +{ + "branding": "AllTalk ", + "tgwui": { + "tgwui_activate_tts": true, + "tgwui_autoplay_tts": true, + "tgwui_narrator_enabled": "false", + "tgwui_non_quoted_text_is": "narrator", + "tgwui_deepspeed_enabled": false, + "tgwui_language": "English", + "tgwui_lowvram_enabled": false, + "tgwui_pitch_set": 0, + "tgwui_temperature_set": 0.75, + "tgwui_repetitionpenalty_set": 10, + "tgwui_generationspeed_set": 1, + "tgwui_narrator_voice": "Please Refresh Settings", + "tgwui_show_text": true, + "tgwui_character_voice": "Please Refresh Settings", + "tgwui_rvc_char_voice": "Disabled", + "tgwui_rvc_narr_voice": "Disabled" + }, + "api_def": { + "api_use_legacy_api": false, + "api_alltalk_protocol": "http://", + "api_alltalk_ip_port": "127.0.0.1:7851", + "api_connection_timeout": 5 + } +} \ No newline at end of file diff --git a/system/admin.html b/system/admin.html new file mode 100644 index 0000000..85add81 --- /dev/null +++ b/system/admin.html @@ -0,0 +1,864 @@ + + + + + + + + AllTalk TTS + + + + + + +

AllTalk TTS

+
+
+
+

Useful links

+ +
+
+

Support Links

+ +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + +
+
+ +
+ + {{ output_file_path }} +
+
+ +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
+
+
+

🛠️ About this project

+

AllTalk is a labour of love, developed and supported in my personal free time. As such, my ability to respond to + support requests is limited. I prioritize issues based on their impact and the number of users affected. I + appreciate your understanding and patience. If your inquiry isn't covered by the documentation or existing + discussions, and it's not related to a bug or feature request, I'll do my best to assist as time allows. +

+
+

💖 Showing Your Support

+

If AllTalk has been helpful to you, consider showing your support through a donation on my Ko-fi page. Your support is greatly appreciated and helps ensure the + continued development and improvement of AllTalk.

+
+

⚠️ Changing Available Engine

+
+

Changing the Available Engine on this web page will cause the Gradio interface to become + unsynchronized with the TTS Engine process until AllTalk is restarted. This may result in issues with + the Gradio interface, including warnings such as [Errno 10048] error while attempting to bind on + address or runner errors when exiting AllTalk. For best results, please use + the Gradio interface to change and manage your Available Engine" +

+
+ + + + +
+ + + \ No newline at end of file diff --git a/system/at_admin/3c6fde566d6a.jpg b/system/at_admin/3c6fde566d6a.jpg new file mode 100644 index 0000000..b0ea6f6 Binary files /dev/null and b/system/at_admin/3c6fde566d6a.jpg differ diff --git a/system/at_admin/4abed1ca.jpg b/system/at_admin/4abed1ca.jpg new file mode 100644 index 0000000..8071be9 Binary files /dev/null and b/system/at_admin/4abed1ca.jpg differ diff --git a/system/at_admin/at_settings.html b/system/at_admin/at_settings.html new file mode 100644 index 0000000..936ae6c --- /dev/null +++ b/system/at_admin/at_settings.html @@ -0,0 +1,248 @@ + + + + + + + AllTalk Startup Settings + + + + +

AllTalk Startup Settings


+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ + modeldownload_model_path }} + + + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+

WARNING: Changing RED items or setting DeepSpeed to True on a system without DeepSpeed installed, + may cause errors.

+ + + + \ No newline at end of file diff --git a/system/at_admin/atandsdplugin.jpg b/system/at_admin/atandsdplugin.jpg new file mode 100644 index 0000000..a006dd3 Binary files /dev/null and b/system/at_admin/atandsdplugin.jpg differ diff --git a/system/at_admin/deepspeedexample.jpg b/system/at_admin/deepspeedexample.jpg new file mode 100644 index 0000000..87120b1 Binary files /dev/null and b/system/at_admin/deepspeedexample.jpg differ diff --git a/system/at_admin/favicon.ico b/system/at_admin/favicon.ico new file mode 100644 index 0000000..1e0d7ff Binary files /dev/null and b/system/at_admin/favicon.ico differ diff --git a/system/at_admin/lowvrammode.png b/system/at_admin/lowvrammode.png new file mode 100644 index 0000000..0c04c7e Binary files /dev/null and b/system/at_admin/lowvrammode.png differ diff --git a/system/at_admin/pythonenvironment.jpg b/system/at_admin/pythonenvironment.jpg new file mode 100644 index 0000000..941f698 Binary files /dev/null and b/system/at_admin/pythonenvironment.jpg differ diff --git a/system/at_admin/pythonrequirementswindows.jpg b/system/at_admin/pythonrequirementswindows.jpg new file mode 100644 index 0000000..fa74d3b Binary files /dev/null and b/system/at_admin/pythonrequirementswindows.jpg differ diff --git a/system/at_admin/textnotinside.jpg b/system/at_admin/textnotinside.jpg new file mode 100644 index 0000000..9744d9a Binary files /dev/null and b/system/at_admin/textnotinside.jpg differ diff --git a/system/at_sounds/apilocal.wav b/system/at_sounds/apilocal.wav new file mode 100644 index 0000000..1e7d818 Binary files /dev/null and b/system/at_sounds/apilocal.wav differ diff --git a/system/at_sounds/apitts.wav b/system/at_sounds/apitts.wav new file mode 100644 index 0000000..ef28fb2 Binary files /dev/null and b/system/at_sounds/apitts.wav differ diff --git a/system/at_sounds/changingmodel.wav b/system/at_sounds/changingmodel.wav new file mode 100644 index 0000000..31b2f20 Binary files /dev/null and b/system/at_sounds/changingmodel.wav differ diff --git a/system/at_sounds/deepspeeddisabled.wav b/system/at_sounds/deepspeeddisabled.wav new file mode 100644 index 0000000..e276b47 Binary files /dev/null and b/system/at_sounds/deepspeeddisabled.wav differ diff --git a/system/at_sounds/deepspeedenabled.wav b/system/at_sounds/deepspeedenabled.wav new file mode 100644 index 0000000..e711b0f Binary files /dev/null and b/system/at_sounds/deepspeedenabled.wav differ diff --git a/system/at_sounds/lowvramdisabled.wav b/system/at_sounds/lowvramdisabled.wav new file mode 100644 index 0000000..ac6fef9 Binary files /dev/null and b/system/at_sounds/lowvramdisabled.wav differ diff --git a/system/at_sounds/lowvramenabled.wav b/system/at_sounds/lowvramenabled.wav new file mode 100644 index 0000000..e165bb5 Binary files /dev/null and b/system/at_sounds/lowvramenabled.wav differ diff --git a/system/at_sounds/xttsfinetuned.wav b/system/at_sounds/xttsfinetuned.wav new file mode 100644 index 0000000..6bddcfd Binary files /dev/null and b/system/at_sounds/xttsfinetuned.wav differ diff --git a/system/at_sounds/xttslocal.wav b/system/at_sounds/xttslocal.wav new file mode 100644 index 0000000..0129168 Binary files /dev/null and b/system/at_sounds/xttslocal.wav differ diff --git a/system/config/AllTalk API Process.png b/system/config/AllTalk API Process.png new file mode 100644 index 0000000..a5ef402 Binary files /dev/null and b/system/config/AllTalk API Process.png differ diff --git a/system/config/at_configdowngrade.json b/system/config/at_configdowngrade.json new file mode 100644 index 0000000..7a73a41 --- /dev/null +++ b/system/config/at_configdowngrade.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/system/config/at_configupdate.json b/system/config/at_configupdate.json new file mode 100644 index 0000000..7a73a41 --- /dev/null +++ b/system/config/at_configupdate.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/system/config/fairseq-0.12.3.1-cp311-cp311-linux_x86_64.whl b/system/config/fairseq-0.12.3.1-cp311-cp311-linux_x86_64.whl new file mode 100644 index 0000000..3fec2c6 Binary files /dev/null and b/system/config/fairseq-0.12.3.1-cp311-cp311-linux_x86_64.whl differ diff --git a/system/config/fairseq-0.12.4-cp311-cp311-win_amd64.whl b/system/config/fairseq-0.12.4-cp311-cp311-win_amd64.whl new file mode 100644 index 0000000..92f0a17 Binary files /dev/null and b/system/config/fairseq-0.12.4-cp311-cp311-win_amd64.whl differ diff --git a/system/config/firstrun.py b/system/config/firstrun.py new file mode 100644 index 0000000..ef19f30 --- /dev/null +++ b/system/config/firstrun.py @@ -0,0 +1,186 @@ +import json +import os +import time +import shutil +import zipfile +import requests +from tqdm import tqdm +from inputimeout import inputimeout, TimeoutOccurred +from pathlib import Path + +this_dir = Path(__file__).parent.resolve().parent.resolve().parent.resolve() +# File paths +config_path = this_dir / 'confignew.json' +tts_engines_path = this_dir / 'system/tts_engines/tts_engines.json' +new_config_template_path = this_dir / 'system/config/confignew.json' + +def download_file(url, dest_path): + response = requests.get(url, stream=True) + total_size = int(response.headers.get('content-length', 0)) + block_size = 1024 # 1 Kibibyte + t = tqdm(total=total_size, unit='iB', unit_scale=True, desc=str(dest_path)) + with open(dest_path, 'wb') as file: + for data in response.iter_content(block_size): + t.update(len(data)) + file.write(data) + t.close() + +def setup_piper(): + os.makedirs(this_dir / 'models/piper', exist_ok=True) + os.makedirs(this_dir / 'models/xtts', exist_ok=True) + download_file("https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/ljspeech/high/en_US-ljspeech-high.onnx?download=true", + this_dir / "models/piper/en_US-ljspeech-high.onnx") + download_file("https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/ljspeech/high/en_US-ljspeech-high.onnx.json?download=true.json", + this_dir / "models/piper/en_US-ljspeech-high.onnx.json") + +def setup_vits(): + os.makedirs(this_dir / 'models/vits', exist_ok=True) + os.makedirs(this_dir / 'models/xtts', exist_ok=True) + zip_path = this_dir / 'models/vits/vits.zip' + download_file("https://coqui.gateway.scarf.sh/v0.6.1_models/tts_models--en--vctk--vits.zip", zip_path) + with zipfile.ZipFile(zip_path, 'r') as zip_ref: + zip_ref.extractall(this_dir / 'models/vits') + os.remove(zip_path) + +def setup_xtts(): + os.makedirs(this_dir / 'models/xtts/xttsv2_2.0.3', exist_ok=True) + base_url = "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/" + files = [ + "LICENSE.txt", + "README.md", + "config.json", + "model.pth", + "dvae.pth", + "mel_stats.pth", + "speakers_xtts.pth", + "vocab.json" + ] + for file in files: + download_file(base_url + file + "?download=true", this_dir / f"models/xtts/xttsv2_2.0.3/{file}") + + + +def update_tts_engines(engine, model): + with open(tts_engines_path, 'r') as file: + tts_engines = json.load(file) + + tts_engines['engine_loaded'] = engine + tts_engines['selected_model'] = model + + with open(tts_engines_path, 'w') as file: + json.dump(tts_engines, file, indent=4) + +def set_firstrun_model_false(): + with open(config_path, 'r') as file: + config = json.load(file) + + config['firstrun_model'] = False + + with open(config_path, 'w') as file: + json.dump(config, file, indent=4) + +def load_config(file_path): + with open(file_path, "r") as config_file: + config = json.load(config_file) + return config + +def warning_mesasge(): + print(f"[{branding}TTS]") + print(f"[{branding}TTS] \033[93mIf you have you have UPGRADED from v1 ensure you have re-installed\033[0m") + print(f"[{branding}TTS] \033[93mthe requirements. Otherwise you will get failures and errors!\033[0m") + print(f"[{branding}TTS] \033[93mOn Linux ignore the \033[0m'sparse_attn requires a torch version' \033[93mand\033[0m") + print(f"[{branding}TTS] \033[0m'using untested triton version' \033[93mmessages.\033[0m") + print(f"[{branding}TTS]") + +# Load confignew.json +with open(config_path, 'r') as file: + config = json.load(file) + +branding = config['branding'] + +# Check if the config file is version 1 +if "ip_address" in config: + print(f"[{branding}TTS] Detected version 1 configuration file.") + print(f"[{branding}TTS] Upgrading to version 2 configuration file...") + + # Backup the old configuration file + backup_path = config_path + ".backup" + shutil.copyfile(config_path, backup_path) + + # Copy the new configuration template to replace the old config file + shutil.copyfile(new_config_template_path, config_path) + + print(f"[{branding}TTS] Configuration file upgraded successfully.") + + # Reload the new config file + with open(config_path, 'r') as file: + config = json.load(file) + +# Check if firstrun_model is true +if config.get('firstrun_model', False): + print(f"[{branding}TTS]") + print(f"[{branding}TTS] \033[92mThis is the first time startup. Please download a start TTS model.\033[0m") + print(f"[{branding}TTS]") + + # List of available models + models = [ + {"name": "piper", "model": "piper"}, + {"name": "vits", "model": "tts_models--en--vctk--vits"}, + {"name": "xtts", "model": "xttsv2_2.0.3"}, + ] + + # Display models to the user + print(f"[{branding}TTS] \033[94mAvailable First Time Start-up models:\033[0m") + print(f"[{branding}TTS]") + for idx, model in enumerate(models): + print(f"[{branding}TTS] \033[93m{idx + 1}. \033[94m{model['name']} - {model['model']}\033[0m") + print(f"[{branding}TTS] \033[93m{len(models) + 1}.\033[94m I have my own models already\033[0m") + print(f"[{branding}TTS]") + print(f"[{branding}TTS] \033[94mIn \033[91m60 seconds\033[0m \033[94ma Piper model will be \033[91mdownloaded automatically.\033[0m") + print(f"[{branding}TTS]") + + # Auto-select model after 30 seconds + selected_model = models[0] # Default to the first model (piper) + try: + user_choice = inputimeout(prompt=f"[{branding}TTS] \033[92mEnter your choice 1-4: \033[0m ", timeout=60) + except TimeoutOccurred: + user_choice = None + + if user_choice is None: + print(f"[{branding}TTS]") + print(f"[{branding}TTS] No input received. Proceeding with the default model (piper).") + elif user_choice.lower() == 'own' or (user_choice.isdigit() and int(user_choice) == len(models) + 1): + print(f"[{branding}TTS]") + print(f"[{branding}TTS] If you have your own XTTS models, please move them into the") + print(f"[{branding}TTS] \033[93m/models/xtts/\033[0m folder") + print(f"[{branding}TTS]") + os.makedirs('models/piper', exist_ok=True) + os.makedirs('models/vits', exist_ok=True) + os.makedirs('models/xtts/', exist_ok=True) + update_tts_engines('piper', 'piper') + set_firstrun_model_false() + warning_mesasge() + exit() + + if user_choice is not None and user_choice.isdigit() and 1 <= int(user_choice) <= len(models): + selected_model = models[int(user_choice) - 1] + warning_mesasge() + + if user_choice is None or user_choice.lower() != 'own': + if selected_model['name'] == 'piper': + setup_piper() + update_tts_engines('piper', 'piper') + elif selected_model['name'] == 'vits': + setup_vits() + update_tts_engines('vits', 'vits - tts_models--en--vctk--vits') + elif selected_model['name'] == 'xtts': + setup_xtts() + update_tts_engines('xtts', 'xtts - xttsv2_2.0.3') + + print(f"[{branding}TTS] {selected_model['name']} model downloaded and configuration updated successfully.") + # Set firstrun_model to false + set_firstrun_model_false() + warning_mesasge() + exit() +else: + exit() diff --git a/system/config/firstrun_tgwui.py b/system/config/firstrun_tgwui.py new file mode 100644 index 0000000..ef19f30 --- /dev/null +++ b/system/config/firstrun_tgwui.py @@ -0,0 +1,186 @@ +import json +import os +import time +import shutil +import zipfile +import requests +from tqdm import tqdm +from inputimeout import inputimeout, TimeoutOccurred +from pathlib import Path + +this_dir = Path(__file__).parent.resolve().parent.resolve().parent.resolve() +# File paths +config_path = this_dir / 'confignew.json' +tts_engines_path = this_dir / 'system/tts_engines/tts_engines.json' +new_config_template_path = this_dir / 'system/config/confignew.json' + +def download_file(url, dest_path): + response = requests.get(url, stream=True) + total_size = int(response.headers.get('content-length', 0)) + block_size = 1024 # 1 Kibibyte + t = tqdm(total=total_size, unit='iB', unit_scale=True, desc=str(dest_path)) + with open(dest_path, 'wb') as file: + for data in response.iter_content(block_size): + t.update(len(data)) + file.write(data) + t.close() + +def setup_piper(): + os.makedirs(this_dir / 'models/piper', exist_ok=True) + os.makedirs(this_dir / 'models/xtts', exist_ok=True) + download_file("https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/ljspeech/high/en_US-ljspeech-high.onnx?download=true", + this_dir / "models/piper/en_US-ljspeech-high.onnx") + download_file("https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/ljspeech/high/en_US-ljspeech-high.onnx.json?download=true.json", + this_dir / "models/piper/en_US-ljspeech-high.onnx.json") + +def setup_vits(): + os.makedirs(this_dir / 'models/vits', exist_ok=True) + os.makedirs(this_dir / 'models/xtts', exist_ok=True) + zip_path = this_dir / 'models/vits/vits.zip' + download_file("https://coqui.gateway.scarf.sh/v0.6.1_models/tts_models--en--vctk--vits.zip", zip_path) + with zipfile.ZipFile(zip_path, 'r') as zip_ref: + zip_ref.extractall(this_dir / 'models/vits') + os.remove(zip_path) + +def setup_xtts(): + os.makedirs(this_dir / 'models/xtts/xttsv2_2.0.3', exist_ok=True) + base_url = "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/" + files = [ + "LICENSE.txt", + "README.md", + "config.json", + "model.pth", + "dvae.pth", + "mel_stats.pth", + "speakers_xtts.pth", + "vocab.json" + ] + for file in files: + download_file(base_url + file + "?download=true", this_dir / f"models/xtts/xttsv2_2.0.3/{file}") + + + +def update_tts_engines(engine, model): + with open(tts_engines_path, 'r') as file: + tts_engines = json.load(file) + + tts_engines['engine_loaded'] = engine + tts_engines['selected_model'] = model + + with open(tts_engines_path, 'w') as file: + json.dump(tts_engines, file, indent=4) + +def set_firstrun_model_false(): + with open(config_path, 'r') as file: + config = json.load(file) + + config['firstrun_model'] = False + + with open(config_path, 'w') as file: + json.dump(config, file, indent=4) + +def load_config(file_path): + with open(file_path, "r") as config_file: + config = json.load(config_file) + return config + +def warning_mesasge(): + print(f"[{branding}TTS]") + print(f"[{branding}TTS] \033[93mIf you have you have UPGRADED from v1 ensure you have re-installed\033[0m") + print(f"[{branding}TTS] \033[93mthe requirements. Otherwise you will get failures and errors!\033[0m") + print(f"[{branding}TTS] \033[93mOn Linux ignore the \033[0m'sparse_attn requires a torch version' \033[93mand\033[0m") + print(f"[{branding}TTS] \033[0m'using untested triton version' \033[93mmessages.\033[0m") + print(f"[{branding}TTS]") + +# Load confignew.json +with open(config_path, 'r') as file: + config = json.load(file) + +branding = config['branding'] + +# Check if the config file is version 1 +if "ip_address" in config: + print(f"[{branding}TTS] Detected version 1 configuration file.") + print(f"[{branding}TTS] Upgrading to version 2 configuration file...") + + # Backup the old configuration file + backup_path = config_path + ".backup" + shutil.copyfile(config_path, backup_path) + + # Copy the new configuration template to replace the old config file + shutil.copyfile(new_config_template_path, config_path) + + print(f"[{branding}TTS] Configuration file upgraded successfully.") + + # Reload the new config file + with open(config_path, 'r') as file: + config = json.load(file) + +# Check if firstrun_model is true +if config.get('firstrun_model', False): + print(f"[{branding}TTS]") + print(f"[{branding}TTS] \033[92mThis is the first time startup. Please download a start TTS model.\033[0m") + print(f"[{branding}TTS]") + + # List of available models + models = [ + {"name": "piper", "model": "piper"}, + {"name": "vits", "model": "tts_models--en--vctk--vits"}, + {"name": "xtts", "model": "xttsv2_2.0.3"}, + ] + + # Display models to the user + print(f"[{branding}TTS] \033[94mAvailable First Time Start-up models:\033[0m") + print(f"[{branding}TTS]") + for idx, model in enumerate(models): + print(f"[{branding}TTS] \033[93m{idx + 1}. \033[94m{model['name']} - {model['model']}\033[0m") + print(f"[{branding}TTS] \033[93m{len(models) + 1}.\033[94m I have my own models already\033[0m") + print(f"[{branding}TTS]") + print(f"[{branding}TTS] \033[94mIn \033[91m60 seconds\033[0m \033[94ma Piper model will be \033[91mdownloaded automatically.\033[0m") + print(f"[{branding}TTS]") + + # Auto-select model after 30 seconds + selected_model = models[0] # Default to the first model (piper) + try: + user_choice = inputimeout(prompt=f"[{branding}TTS] \033[92mEnter your choice 1-4: \033[0m ", timeout=60) + except TimeoutOccurred: + user_choice = None + + if user_choice is None: + print(f"[{branding}TTS]") + print(f"[{branding}TTS] No input received. Proceeding with the default model (piper).") + elif user_choice.lower() == 'own' or (user_choice.isdigit() and int(user_choice) == len(models) + 1): + print(f"[{branding}TTS]") + print(f"[{branding}TTS] If you have your own XTTS models, please move them into the") + print(f"[{branding}TTS] \033[93m/models/xtts/\033[0m folder") + print(f"[{branding}TTS]") + os.makedirs('models/piper', exist_ok=True) + os.makedirs('models/vits', exist_ok=True) + os.makedirs('models/xtts/', exist_ok=True) + update_tts_engines('piper', 'piper') + set_firstrun_model_false() + warning_mesasge() + exit() + + if user_choice is not None and user_choice.isdigit() and 1 <= int(user_choice) <= len(models): + selected_model = models[int(user_choice) - 1] + warning_mesasge() + + if user_choice is None or user_choice.lower() != 'own': + if selected_model['name'] == 'piper': + setup_piper() + update_tts_engines('piper', 'piper') + elif selected_model['name'] == 'vits': + setup_vits() + update_tts_engines('vits', 'vits - tts_models--en--vctk--vits') + elif selected_model['name'] == 'xtts': + setup_xtts() + update_tts_engines('xtts', 'xtts - xttsv2_2.0.3') + + print(f"[{branding}TTS] {selected_model['name']} model downloaded and configuration updated successfully.") + # Set firstrun_model to false + set_firstrun_model_false() + warning_mesasge() + exit() +else: + exit() diff --git a/system/config/fixsymlinks.sh b/system/config/fixsymlinks.sh new file mode 100644 index 0000000..3c519ea --- /dev/null +++ b/system/config/fixsymlinks.sh @@ -0,0 +1,92 @@ +#!/bin/bash + +# Get the conda environment path +env_path="${CONDA_DEFAULT_ENV}/lib" + +# List of broken symlinks +declare -A symlinks=( + ["libcufile.so"]="libcufile.so" + ["libcufile_rdma.so"]="libcufile_rdma.so" + ["libcudart.so"]="libcudart.so" + ["libnvjpeg.so"]="libnvjpeg.so" + ["libcurand.so"]="libcurand.so" + ["libnvJitLink.so"]="libnvJitLink.so" + ["libnvrtc-builtins.so"]="libnvrtc-builtins.so" + ["libnvrtc.so"]="libnvrtc.so" +) + +# Function to test for broken symlinks +test_broken_symlinks() { + echo "Testing for broken symlinks..." + broken_found=false + for link in "${!symlinks[@]}"; do + if [ -L "$env_path/$link" ] && [ ! -e "$env_path/$link" ]; then + echo "Broken symlink found: $env_path/$link" + broken_found=true + fi + done + if [ "$broken_found" = false ]; then + echo "No broken symlinks found." + fi +} + +# Function to fix broken symlinks +fix_broken_symlinks() { + echo "Fixing broken symlinks..." + for link in "${!symlinks[@]}"; do + if [ -L "$env_path/$link" ] && [ ! -e "$env_path/$link" ]; then + echo "Removing broken link: $env_path/$link" + rm "$env_path/$link" + + # Find all possible target files + target_files=($(find "$env_path" -name "${symlinks[$link]}*")) + + if [ ${#target_files[@]} -gt 0 ]; then + echo "Select a target file for $link:" + for i in "${!target_files[@]}"; do + echo "$((i+1)). ${target_files[$i]}" + done + + read -p "Enter the number of the target file to use: " choice + choice=$((choice-1)) + + if [[ $choice -ge 0 && $choice -lt ${#target_files[@]} ]]; then + target="${target_files[$choice]}" + echo "Creating new symlink: $env_path/$link -> $target" + ln -s "$target" "$env_path/$link" + else + echo "Invalid choice, skipping $link." + fi + else + echo "No target files found for $link." + fi + fi + done + echo "Verification of symbolic links:" + ls -l $env_path | grep -E "libcufile.so|libcudart.so|libcufile_rdma.so|libnvjpeg.so|libcurand.so|libnvJitLink.so|libnvrtc-builtins.so|libnvrtc.so" +} + +# Menu +while true; do + echo "Select an option:" + echo "1. Test for broken symlinks" + echo "2. Fix broken symlinks" + echo "3. Exit" + read -p "Enter your choice [1-3]: " choice + case $choice in + 1) + test_broken_symlinks + ;; + 2) + fix_broken_symlinks + ;; + 3) + echo "Exiting." + break + ;; + *) + echo "Invalid choice. Please select 1, 2, or 3." + ;; + esac +done + diff --git a/system/config/harvard_sentences.txt b/system/config/harvard_sentences.txt new file mode 100644 index 0000000..958d7f3 --- /dev/null +++ b/system/config/harvard_sentences.txt @@ -0,0 +1,720 @@ +The birch canoe slid on the smooth planks. +Glue the sheet to the dark blue background. +It's easy to tell the depth of a well. +These days a chicken leg is a rare dish. +Rice is often served in round bowls. +The juice of lemons makes fine punch. +The box was thrown beside the parked truck. +The hogs were fed chopped corn and garbage. +Four hours of steady work faced us. +A large size in stockings is hard to sell. +The boy was there when the sun rose. +A rod is used to catch pink salmon. +The source of the huge river is the clear spring. +Kick the ball straight and follow through. +Help the woman get back to her feet. +A pot of tea helps to pass the evening. +Smoky fires lack flame and heat. +The soft cushion broke the man's fall. +The salt breeze came across from the sea. +The girl at the booth sold fifty bonds. +The small pup gnawed a hole in the sock. +The fish twisted and turned on the bent hook. +Press the pants and sew a button on the vest. +The swan dive was far short of perfect. +The beauty of the view stunned the young boy. +Two blue fish swam in the tank. +Her purse was full of useless trash. +The colt reared and threw the tall rider. +It snowed, rained, and hailed the same morning. +Read verse out loud for pleasure. +Hoist the load to your left shoulder. +Take the winding path to reach the lake. +Note closely the size of the gas tank. +Wipe the grease off his dirty face. +Mend the coat before you go out. +The wrist was badly strained and hung limp. +The stray cat gave birth to kittens. +The young girl gave no clear response. +The meal was cooked before the bell rang. +What joy there is in living. +A king ruled the state in the early days. +The ship was torn apart on the sharp reef. +Sickness kept him home the third week. +The wide road shimmered in the hot sun. +The lazy cow lay in the cool grass. +Lift the square stone over the fence. +The rope will bind the seven books at once. +Hop over the fence and plunge in. +The friendly gang left the drug store. +Mesh wire keeps chicks inside. +The frosty air passed through the coat. +The crooked maze failed to fool the mouse. +Adding fast leads to wrong sums. +The show was a flop from the very start. +A saw is a tool used for making boards. +The wagon moved on well oiled wheels. +March the soldiers past the next hill. +A cup of sugar makes sweet fudge. +Place a rosebush near the porch steps. +Both lost their lives in the raging storm. +We talked of the side show in the circus. +Use a pencil to write the first draft. +He ran half way to the hardware store. +The clock struck to mark the third period. +A small creek cut across the field. +Cars and busses stalled in snow drifts. +The set of china hit the floor with a crash. +This is a grand season for hikes on the road. +The dune rose from the edge of the water. +Those words were the cue for the actor to leave. +A yacht slid around the point into the bay. +The two met while playing on the sand. +The ink stain dried on the finished page. +The walled town was seized without a fight. +The lease ran out in sixteen weeks. +A tame squirrel makes a nice pet. +The horn of the car woke the sleeping cop. +The heart beat strongly and with firm strokes. +The pearl was worn in a thin silver ring. +The fruit peel was cut in thick slices. +The Navy attacked the big task force. +See the cat glaring at the scared mouse. +There are more than two factors here. +The hat brim was wide and too droopy. +The lawyer tried to lose his case. +The grass curled around the fence post. +Cut the pie into large parts. +Men strive but seldom get rich. +Always close the barn door tight. +He lay prone and hardly moved a limb. +The slush lay deep along the street. +A wisp of cloud hung in the blue air. +A pound of sugar costs more than eggs. +The fin was sharp and cut the clear water. +The play seems dull and quite stupid. +Bail the boat to stop it from sinking. +The term ended in late June that year. +A tusk is used to make costly gifts. +Ten pins were set in order. +The bill was paid every third week. +Oak is strong and also gives shade. +Cats and dogs each hate the other. +The pipe began to rust while new. +Open the crate but don't break the glass. +Add the sum to the product of these three. +Thieves who rob friends deserve jail. +The ripe taste of cheese improves with age. +Act on these orders with great speed. +The hog crawled under the high fence. +Move the vat over the hot fire. +The bark of the pine tree was shiny and dark. +Leaves turn brown and yellow in the fall. +The pennant waved when the wind blew. +Split the log with a quick, sharp blow. +Burn peat after the logs give out. +He ordered peach pie with ice cream. +Weave the carpet on the right hand side. +Hemp is a weed found in parts of the tropics. +A lame back kept his score low. +We find joy in the simplest things. +Type out three lists of orders. +The harder he tried the less he got done. +The boss ran the show with a watchful eye. +The cup cracked and spilled its contents. +Paste can cleanse the most dirty brass. +The slang word for raw whiskey is booze. +It caught its hind paw in a rusty trap. +The wharf could be seen at the farther shore. +Feel the heat of the weak dying flame. +The tiny girl took off her hat. +A cramp is no small danger on a swim. +He said the same phrase thirty times. +Pluck the bright rose without leaves. +Two plus seven is less than ten. +The glow deepened in the eyes of the sweet girl. +Bring your problems to the wise chief. +Write a fond note to the friend you cherish. +Clothes and lodging are free to new men. +We frown when events take a bad turn. +Port is a strong wine with a smoky taste. +The young kid jumped the rusty gate. +Guess the results from the first scores. +A salt pickle tastes fine with ham. +The just claim got the right verdict. +These thistles bend in a high wind. +Pure bred poodles have curls. +The tree top waved in a graceful way. +The spot on the blotter was made by green ink. +Mud was spattered on the front of his white shirt. +The cigar burned a hole in the desk top. +The empty flask stood on the tin tray. +A speedy man can beat this track mark. +He broke a new shoelace that day. +The coffee stand is too high for the couch. +The urge to write short stories is rare. +The pencils have all been used. +The pirates seized the crew of the lost ship. +We tried to replace the coin but failed. +She sewed the torn coat quite neatly. +The sofa cushion is red and of light weight. +The jacket hung on the back of the wide chair. +At that high level the air is pure. +Drop the two when you add the figures. +A filing case is now hard to buy. +An abrupt start does not win the prize. +Wood is best for making toys and blocks. +The office paint was a dull, sad tan. +He knew the skill of the great young actress. +A rag will soak up spilled water. +A shower of dirt fell from the hot pipes. +Steam hissed from the broken valve. +The child almost hurt the small dog. +There was a sound of dry leaves outside. +The sky that morning was clear and bright blue. +Torn scraps littered the stone floor. +Sunday is the best part of the week. +The doctor cured him with these pills. +The new girl was fired today at noon. +They felt gay when the ship arrived in port. +Add the store's account to the last cent. +Acid burns holes in wool cloth. +Fairy tales should be fun to write. +Eight miles of woodland burned to waste. +The third act was dull and tired the players. +A young child should not suffer fright. +Add the column and put the sum here. +We admire and love a good cook. +There the flood mark is ten inches. +He carved a head from the round block of marble. +She has a smart way of wearing clothes. +The fruit of a fig tree is apple-shaped. +Corn cobs can be used to kindle a fire. +Where were they when the noise started. +The paper box is full of thumb tacks. +Sell your gift to a buyer at a good gain. +The tongs lay beside the ice pail. +The petals fall with the next puff of wind. +Bring your best compass to the third class. +They could laugh although they were sad. +Farmers came in to thresh the oat crop. +The brown house was on fire to the attic. +The lure is used to catch trout and flounder. +Float the soap on top of the bath water. +A blue crane is a tall wading bird. +A fresh start will work such wonders. +The club rented the rink for the fifth night. +After the dance, they went straight home. +The hostess taught the new maid to serve. +He wrote his last novel there at the inn. +Even the worst will beat his low score. +The cement had dried when he moved it. +The loss of the second ship was hard to take. +The fly made its way along the wall. +Do that with a wooden stick. +Live wires should be kept covered. +The large house had hot water taps. +It is hard to erase blue or red ink. +Write at once or you may forget it. +The doorknob was made of bright clean brass. +The wreck occurred by the bank on Main Street. +A pencil with black lead writes best. +Coax a young calf to drink from a bucket. +Schools for ladies teach charm and grace. +The lamp shone with a steady green flame. +They took the axe and the saw to the forest. +The ancient coin was quite dull and worn. +The shaky barn fell with a loud crash. +Jazz and swing fans like fast music. +Rake the rubbish up and then burn it. +Slash the gold cloth into fine ribbons. +Try to have the court decide the case. +They are pushed back each time they attack. +He broke his ties with groups of former friends. +They floated on the raft to sun their white backs. +The map had an X that meant nothing. +Whitings are small fish caught in nets. +Some ads serve to cheat buyers. +Jerk the rope and the bell rings weakly. +A waxed floor makes us lose balance. +Madam, this is the best brand of corn. +On the islands the sea breeze is soft and mild. +The play began as soon as we sat down. +This will lead the world to more sound and fury. +Add salt before you fry the egg. +The rush for funds reached its peak Tuesday. +The birch looked stark white and lonesome. +The box is held by a bright red snapper. +To make pure ice, you freeze water. +The first worm gets snapped early. +Jump the fence and hurry up the bank. +Yell and clap as the curtain slides back. +They are men who walk the middle of the road. +Both brothers wear the same size. +In some form or other we need fun. +The prince ordered his head chopped off. +The houses are built of red clay bricks. +Ducks fly north but lack a compass. +Fruit flavors are used in fizz drinks. +These pills do less good than others. +Canned pears lack full flavor. +The dark pot hung in the front closet. +Carry the pail to the wall and spill it there. +The train brought our hero to the big town. +We are sure that one war is enough. +Gray paint stretched for miles around. +The rude laugh filled the empty room. +High seats are best for football fans. +Tea served from the brown jug is tasty. +A dash of pepper spoils beef stew. +A zestful food is the hot-cross bun. +The horse trotted around the field at a brisk pace. +Find the twin who stole the pearl necklace. +Cut the cord that binds the box tightly. +The red tape bound the smuggled food. +Look in the corner to find the tan shirt. +The cold drizzle will halt the bond drive. +Nine men were hired to dig the ruins. +The junk yard had a mouldy smell. +The flint sputtered and lit a pine torch. +Soak the cloth and drown the sharp odor. +The shelves were bare of both jam or crackers. +A joy to every child is the swan boat. +All sat frozen and watched the screen. +A cloud of dust stung his tender eyes. +To reach the end he needs much courage. +Shape the clay gently into block form. +A ridge on a smooth surface is a bump or flaw. +Hedge apples may stain your hands green. +Quench your thirst, then eat the crackers. +Tight curls get limp on rainy days. +The mute muffled the high tones of the horn. +The gold ring fits only a pierced ear. +The old pan was covered with hard fudge. +Watch the log float in the wide river. +The node on the stalk of wheat grew daily. +The heap of fallen leaves was set on fire. +Write fast if you want to finish early. +His shirt was clean but one button was gone. +The barrel of beer was a brew of malt and hops. +Tin cans are absent from store shelves. +Slide the box into that empty space. +The plant grew large and green in the window. +The beam dropped down on the workmen's head. +Pink clouds floated with the breeze. +She danced like a swan, tall and graceful. +The tube was blown and the tire flat and useless. +It is late morning on the old wall clock. +Let's all join as we sing the last chorus. +The last switch cannot be turned off. +The fight will end in just six minutes. +The store walls were lined with colored frocks. +The peace league met to discuss their plans. +The rise to fame of a person takes luck. +Paper is scarce, so write with much care. +The quick fox jumped on the sleeping cat. +The nozzle of the fire hose was bright brass. +Screw the round cap on as tight as needed. +Time brings us many changes. +The purple tie was ten years old. +Men think and plan and sometimes act. +Fill the ink jar with sticky glue. +He smoke a big pipe with strong contents. +We need grain to keep our mules healthy. +Pack the records in a neat thin case. +The crunch of feet in the snow was the only sound. +The copper bowl shone in the sun's rays. +Boards will warp unless kept dry. +The plush chair leaned against the wall. +Glass will clink when struck by metal. +Bathe and relax in the cool green grass. +Nine rows of soldiers stood in line. +The beach is dry and shallow at low tide. +The idea is to sew both edges straight. +The kitten chased the dog down the street. +Pages bound in cloth make a book. +Try to trace the fine lines of the painting. +Women form less than half of the group. +The zones merge in the central part of town. +A gem in the rough needs work to polish. +Code is used when secrets are sent. +Most of the news is easy for us to hear. +He used the lathe to make brass objects. +The vane on top of the pole revolved in the wind. +Mince pie is a dish served to children. +The clan gathered on each dull night. +Let it burn, it gives us warmth and comfort. +A castle built from sand fails to endure. +A child's wit saved the day for us. +Tack the strip of carpet to the worn floor. +Next Tuesday we must vote. +Pour the stew from the pot into the plate. +Each penny shone like new. +The man went to the woods to gather sticks. +The dirt piles were lines along the road. +The logs fell and tumbled into the clear stream. +Just hoist it up and take it away. +A ripe plum is fit for a king's palate. +Our plans right now are hazy. +Brass rings are sold by these natives. +It takes a good trap to capture a bear. +Feed the white mouse some flower seeds. +The thaw came early and freed the stream. +He took the lead and kept it the whole distance. +The key you designed will fit the lock. +Plead to the council to free the poor thief. +Better hash is made of rare beef. +This plank was made for walking on. +The lake sparkled in the red hot sun. +He crawled with care along the ledge. +Tend the sheep while the dog wanders. +It takes a lot of help to finish these. +Mark the spot with a sign painted red. +Take two shares as a fair profit. +The fur of cats goes by many names. +North winds bring colds and fevers. +He asks no person to vouch for him. +Go now and come here later. +A sash of gold silk will trim her dress. +Soap can wash most dirt away. +That move means the game is over. +He wrote down a long list of items. +A siege will crack the strong defense. +Grape juice and water mix well. +Roads are paved with sticky tar. +Fake stones shine but cost little. +The drip of the rain made a pleasant sound. +Smoke poured out of every crack. +Serve the hot rum to the tired heroes. +Much of the story makes good sense. +The sun came up to light the eastern sky. +Heave the line over the port side. +A lathe cuts and trims any wood. +It's a dense crowd in two distinct ways. +His hip struck the knee of the next player. +The stale smell of old beer lingers. +The desk was firm on the shaky floor. +It takes heat to bring out the odor. +Beef is scarcer than some lamb. +Raise the sail and steer the ship northward. +A cone costs five cents on Mondays. +A pod is what peas always grow in. +Jerk the dart from the cork target. +No cement will hold hard wood. +We now have a new base for shipping. +A list of names is carved around the base. +The sheep were led home by a dog. +Three for a dime, the young peddler cried. +The sense of smell is better than that of touch. +No hardship seemed to keep him sad. +Grace makes up for lack of beauty. +Nudge gently but wake her now. +The news struck doubt into restless minds. +Once we stood beside the shore. +A chink in the wall allowed a draft to blow. +Fasten two pins on each side. +A cold dip restores health and zest. +He takes the oath of office each March. +The sand drifts over the sill of the old house. +The point of the steel pen was bent and twisted. +There is a lag between thought and act. +Seed is needed to plant the spring corn. +Draw the chart with heavy black lines. +The boy owed his pal thirty cents. +The chap slipped into the crowd and was lost. +Hats are worn to tea and not to dinner. +The ramp led up to the wide highway. +Beat the dust from the rug onto the lawn. +Say it slowly but make it ring clear. +The straw nest housed five robins. +Screen the porch with woven straw mats. +This horse will nose his way to the finish. +The dry wax protects the deep scratch. +He picked up the dice for a second roll. +These coins will be needed to pay his debt. +The nag pulled the frail cart along. +Twist the valve and release hot steam. +The vamp of the shoe had a gold buckle. +The smell of burned rags itches my nose. +New pants lack cuffs and pockets. +The marsh will freeze when cold enough. +They slice the sausage thin with a knife. +The bloom of the rose lasts a few days. +A gray mare walked before the colt. +Breakfast buns are fine with a hot drink. +Bottles hold four kinds of rum. +The man wore a feather in his felt hat. +He wheeled the bike past the winding road. +Drop the ashes on the worn old rug. +The desk and both chairs were painted tan. +Throw out the used paper cup and plate. +A clean neck means a neat collar. +The couch cover and hall drapes were blue. +The stems of the tall glasses cracked and broke. +The wall phone rang loud and often. +The clothes dried on a thin wooden rack. +Turn on the lantern which gives us light. +The cleat sank deeply into the soft turf. +The bills were mailed promptly on the tenth of the month. +To have is better than to wait and hope. +The price is fair for a good antique clock. +The music played on while they talked. +Dispense with a vest on a day like this. +The bunch of grapes was pressed into wine. +He sent the figs, but kept the ripe cherries. +The hinge on the door creaked with old age. +The screen before the fire kept in the sparks. +Fly by night, and you waste little time. +Thick glasses helped him read the print. +Birth and death mark the limits of life. +The chair looked strong but had no bottom. +The kite flew wildly in the high wind. +A fur muff is stylish once more. +The tin box held priceless stones. +We need an end of all such matter. +The case was puzzling to the old and wise. +The bright lanterns were gay on the dark lawn. +We don't get much money but we have fun. +The youth drove with zest, but little skill. +Five years he lived with a shaggy dog. +A fence cuts through the corner lot. +The way to save money is not to spend much. +Shut the hatch before the waves push it in. +The odor of spring makes young hearts jump. +Crack the walnut with your sharp side teeth. +He offered proof in the form of a large chart. +Send the stuff in a thick paper bag. +A quart of milk is water for the most part. +They told wild tales to frighten him. +The three story house was built of stone. +In the rear of the ground floor was a large passage. +A man in a blue sweater sat at the desk. +Oats are a food eaten by horse and man. +Their eyelids droop for want of sleep. +A sip of tea revives his tired friend. +There are many ways to do these things. +Tuck the sheet under the edge of the mat. +A force equal to that would move the earth. +We like to see clear weather. +The work of the tailor is seen on each side. +Take a chance and win a china doll. +Shake the dust from your shoes, stranger. +She was kind to sick old people. +The square wooden crate was packed to be shipped. +The dusty bench stood by the stone wall. +We dress to suit the weather of most days. +Smile when you say nasty words. +A bowl of rice is free with chicken stew. +The water in this well is a source of good health. +Take shelter in this tent, but keep still. +That guy is the writer of a few banned books. +The little tales they tell are false. +The door was barred, locked, and bolted as well. +Ripe pears are fit for a queen's table. +A big wet stain was on the round carpet. +The kite dipped and swayed, but stayed aloft. +The pleasant hours fly by much too soon. +The room was crowded with a wild mob. +This strong arm shall shield your honor. +She blushed when he gave her a white orchid. +The beetle droned in the hot June sun. +Press the pedal with your left foot. +Neat plans fail without luck. +The black trunk fell from the landing. +The bank pressed for payment of the debt. +The theft of the pearl pin was kept secret. +Shake hands with this friendly child. +The vast space stretched into the far distance. +A rich farm is rare in this sandy waste. +His wide grin earned many friends. +Flax makes a fine brand of paper. +Hurdle the pit with the aid of a long pole. +A strong bid may scare your partner stiff. +Even a just cause needs power to win. +Peep under the tent and see the clowns. +The leaf drifts along with a slow spin. +Cheap clothes are flashy but don't last. +A thing of small note can cause despair. +Flood the mails with requests for this book. +A thick coat of black paint covered all. +The pencil was cut to be sharp at both ends. +Those last words were a strong statement. +He wrote his name boldly at the top of the sheet. +Dill pickles are sour but taste fine. +Down that road is the way to the grain farmer. +Either mud or dust are found at all times. +The best method is to fix it in place with clips. +If you mumble your speech will be lost. +At night the alarm roused him from a deep sleep. +Read just what the meter says. +Fill your pack with bright trinkets for the poor. +The small red neon lamp went out. +Clams are small, round, soft, and tasty. +The fan whirled its round blades softly. +The line where the edges join was clean. +Breathe deep and smell the piny air. +It matters not if he reads these words or those. +A brown leather bag hung from its strap. +A toad and a frog are hard to tell apart. +A white silk jacket goes with any shoes. +A break in the dam almost caused a flood. +Paint the sockets in the wall dull green. +The child crawled into the dense grass. +Bribes fail where honest men work. +Trample the spark, else the flames will spread. +The hilt of the sword was carved with fine designs. +A round hole was drilled through the thin board. +Footprints showed the path he took up the beach. +She was waiting at my front lawn. +A vent near the edge brought in fresh air. +Prod the old mule with a crooked stick. +It is a band of steel three inches wide. +The pipe ran almost the length of the ditch. +It was hidden from sight by a mass of leaves and shrubs. +The weight of the package was seen on the high scale. +Wake and rise, and step into the green outdoors. +The green light in the brown box flickered. +The brass tube circled the high wall. +The lobes of her ears were pierced to hold rings. +Hold the hammer near the end to drive the nail. +Next Sunday is the twelfth of the month. +Every word and phrase he speaks is true. +He put his last cartridge into the gun and fired. +They took their kids from the public school. +Drive the screw straight into the wood. +Keep the hatch tight and the watch constant. +Sever the twine with a quick snip of the knife. +Paper will dry out when wet. +Slide the catch back and open the desk. +Help the weak to preserve their strength. +A sullen smile gets few friends. +Stop whistling and watch the boys march. +Jerk the cord, and out tumbles the gold. +Slide the tray across the glass top. +The cloud moved in a stately way and was gone. +Light maple makes for a swell room. +Set the piece here and say nothing. +Dull stories make her laugh. +A stiff cord will do to fasten your shoe. +Get the trust fund to the bank early. +Choose between the high road and the low. +A plea for funds seems to come again. +He lent his coat to the tall gaunt stranger. +There is a strong chance it will happen once more. +The duke left the park in a silver coach. +Greet the new guests and leave quickly. +When the frost has come it is time for turkey. +Sweet words work better than fierce. +A thin stripe runs down the middle. +A six comes up more often than a ten. +Lush fern grow on the lofty rocks. +The ram scared the school children off. +The team with the best timing looks good. +The farmer swapped his horse for a brown ox. +Sit on the perch and tell the others what to do. +A steep trail is painful for our feet. +The early phase of life moves fast. +Green moss grows on the northern side. +Tea in thin china has a sweet taste. +Pitch the straw through the door of the stable. +The latch on the back gate needed a nail. +The goose was brought straight from the old market. +The sink is the thing in which we pile dishes. +A whiff of it will cure the most stubborn cold. +The facts don't always show who is right. +She flaps her cape as she parades the street. +The loss of the cruiser was a blow to the fleet. +Loop the braid to the left and then over. +Plead with the lawyer to drop the lost cause. +Calves thrive on tender spring grass. +Post no bills on this office wall. +Tear a thin sheet from the yellow pad. +A cruise in warm waters in a sleek yacht is fun. +A streak of color ran down the left edge. +It was done before the boy could see it. +Crouch before you jump or miss the mark. +Pack the kits and don't forget the salt. +The square peg will settle in the round hole. +Fine soap saves tender skin. +Poached eggs and tea must suffice. +Bad nerves are jangled by a door slam. +Ship maps are different from those for planes. +Dimes showered down from all sides. +They sang the same tunes at each party. +The sky in the west is tinged with orange red. +The pods of peas ferment in bare fields. +The horse balked and threw the tall rider. +The hitch between the horse and cart broke. +Pile the coal high in the shed corner. +A gold vase is both rare and costly. +The knife was hung inside its bright sheath. +The rarest spice comes from the far East. +The roof should be tilted at a sharp slant. +A smatter of French is worse than none. +The mule trod the treadmill day and night. +The aim of the contest is to raise a great fund. +To send it now in large amounts is bad. +There is a fine hard tang in salty air. +Cod is the main business of the north shore. +The slab was hewn from heavy blocks of slate. +Dunk the stale biscuits into strong drink. +Hang tinsel from both branches. +Cap the jar with a tight brass cover. +The poor boy missed the boat again. +Be sure to set the lamp firmly in the hole. +Pick a card and slip it under the pack. +A round mat will cover the dull spot. +The first part of the plan needs changing. +A good book informs of what we ought to know. +The mail comes in three batches per day. +You cannot brew tea in a cold pot. +Dots of light betrayed the black cat. +Put the chart on the mantel and tack it down. +The night shift men rate extra pay. +The red paper brightened the dim stage. +See the player scoot to third base. +Slide the bill between the two leaves. +Many hands help get the job done. +We don't like to admit our small faults. +No doubt about the way the wind blows. +Dig deep in the earth for pirate's gold. +The steady drip is worse than a drenching rain. +A flat pack takes less luggage space. +Green ice frosted the punch bowl. +A stuffed chair slipped from the moving van. +The stitch will serve but needs to be shortened. +A thin book fits in the side pocket. +The gloss on top made it unfit to read. +The hail pattered on the burnt brown grass. +Seven seals were stamped on great sheets. +Our troops are set to strike heavy blows. +The store was jammed before the sale could start. +It was a bad error on the part of the new judge. +One step more and the board will collapse. +Take the match and strike it against your shoe. +The pot boiled, but the contents failed to jell. +The baby puts his right foot in his mouth. +The bombs left most of the town in ruins. +Stop and stare at the hard working man. +The streets are narrow and full of sharp turns. +The pup jerked the leash as he saw a feline shape. +Open your book to the first page. +Fish evade the net and swim off. +Dip the pail once and let it settle. +Will you please answer that phone. +The big red apple fell to the ground. +The curtain rose and the show was on. +The young prince became heir to the throne. +He sent the boy on a short errand. +Leave now and you will arrive on time. +The corner store was robbed last night. +A gold ring will please most any girl. +The long journey home took a year. +She saw a cat in the neighbor's house. +A pink shell was found on the sandy beach. +Small children came to see him. +The grass and bushes were wet with dew. +The blind man counted his old coins. +A severe storm tore down the barn. +She called his name many times. +When you hear the bell, come quickly. \ No newline at end of file diff --git a/system/config/languages.json b/system/config/languages.json new file mode 100644 index 0000000..3895d7f --- /dev/null +++ b/system/config/languages.json @@ -0,0 +1,18 @@ +{ + "Arabic": "ar", + "Chinese": "zh-cn", + "Czech": "cs", + "Dutch": "nl", + "English": "en", + "French": "fr", + "German": "de", + "Hungarian": "hu", + "Italian": "it", + "Japanese": "ja", + "Korean": "ko", + "Polish": "pl", + "Portuguese": "pt", + "Russian": "ru", + "Spanish": "es", + "Turkish": "tr" +} \ No newline at end of file diff --git a/system/config/symlinks_cuda_toolkit_breaks.txt b/system/config/symlinks_cuda_toolkit_breaks.txt new file mode 100644 index 0000000..5ae5269 --- /dev/null +++ b/system/config/symlinks_cuda_toolkit_breaks.txt @@ -0,0 +1,13 @@ +find {addpathheretoscan}/alltalk_environment/env/lib -xtype l + +here are the list on Cuda-Toolkit 12.1, others vary + +.../alltalk_environment/env/lib/libcufile.so +.../alltalk_environment/env/lib/libcudart.so +.../alltalk_environment/env/lib/libcufile_rdma.so +.../alltalk_environment/env/lib/libnvjpeg.so +.../alltalk_environment/env/lib/libcurand.so +.../alltalk_environment/env/lib/libnvJitLink.so +.../alltalk_environment/env/lib/libnvrtc-builtins.so +.../alltalk_environment/env/lib/libnvrtc.so + diff --git a/system/espeak-ng/espeak-ng-X64.msi b/system/espeak-ng/espeak-ng-X64.msi new file mode 100644 index 0000000..d5de3f7 Binary files /dev/null and b/system/espeak-ng/espeak-ng-X64.msi differ diff --git a/system/espeak-ng/espeak-ng-X86.msi b/system/espeak-ng/espeak-ng-X86.msi new file mode 100644 index 0000000..fe1787a Binary files /dev/null and b/system/espeak-ng/espeak-ng-X86.msi differ diff --git a/system/favicon.ico b/system/favicon.ico new file mode 100644 index 0000000..1e0d7ff Binary files /dev/null and b/system/favicon.ico differ diff --git a/system/ft_tokenizer/tokenizer.py b/system/ft_tokenizer/tokenizer.py new file mode 100644 index 0000000..1611d5a --- /dev/null +++ b/system/ft_tokenizer/tokenizer.py @@ -0,0 +1,869 @@ +import os +import re +import textwrap +from functools import cached_property + +import pypinyin +import torch +from hangul_romanize import Transliter +from hangul_romanize.rule import academic +from num2words import num2words +from spacy.lang.ar import Arabic +from spacy.lang.en import English +from spacy.lang.es import Spanish +from spacy.lang.ja import Japanese +from spacy.lang.zh import Chinese +from tokenizers import Tokenizer + +from TTS.tts.layers.xtts.zh_num2words import TextNorm as zh_num2words + + +def get_spacy_lang(lang): + if lang == "zh": + return Chinese() + elif lang == "ja": + return Japanese() + elif lang == "ar": + return Arabic() + elif lang == "es": + return Spanish() + else: + # For most languages, Enlish does the job + return English() + + +def split_sentence(text, lang, text_split_length=250): + """Preprocess the input text""" + text_splits = [] + if text_split_length is not None and len(text) >= text_split_length: + text_splits.append("") + nlp = get_spacy_lang(lang) + nlp.add_pipe("sentencizer") + doc = nlp(text) + for sentence in doc.sents: + if len(text_splits[-1]) + len(str(sentence)) <= text_split_length: + # if the last sentence + the current sentence is less than the text_split_length + # then add the current sentence to the last sentence + text_splits[-1] += " " + str(sentence) + text_splits[-1] = text_splits[-1].lstrip() + elif len(str(sentence)) > text_split_length: + # if the current sentence is greater than the text_split_length + for line in textwrap.wrap( + str(sentence), + width=text_split_length, + drop_whitespace=True, + break_on_hyphens=False, + tabsize=1, + ): + text_splits.append(str(line)) + else: + text_splits.append(str(sentence)) + + if len(text_splits) > 1: + if text_splits[0] == "": + del text_splits[0] + else: + text_splits = [text.lstrip()] + + return text_splits + + +_whitespace_re = re.compile(r"\s+") + +# List of (regular expression, replacement) pairs for abbreviations: +_abbreviations = { + "en": [ + (re.compile("\\b%s\\." % x[0], re.IGNORECASE), x[1]) + for x in [ + ("mrs", "misess"), + ("mr", "mister"), + ("dr", "doctor"), + ("st", "saint"), + ("co", "company"), + ("jr", "junior"), + ("maj", "major"), + ("gen", "general"), + ("drs", "doctors"), + ("rev", "reverend"), + ("lt", "lieutenant"), + ("hon", "honorable"), + ("sgt", "sergeant"), + ("capt", "captain"), + ("esq", "esquire"), + ("ltd", "limited"), + ("col", "colonel"), + ("ft", "fort"), + ] + ], + "es": [ + (re.compile("\\b%s\\." % x[0], re.IGNORECASE), x[1]) + for x in [ + ("sra", "señora"), + ("sr", "señor"), + ("dr", "doctor"), + ("dra", "doctora"), + ("st", "santo"), + ("co", "compañía"), + ("jr", "junior"), + ("ltd", "limitada"), + ] + ], + "fr": [ + (re.compile("\\b%s\\." % x[0], re.IGNORECASE), x[1]) + for x in [ + ("mme", "madame"), + ("mr", "monsieur"), + ("dr", "docteur"), + ("st", "saint"), + ("co", "compagnie"), + ("jr", "junior"), + ("ltd", "limitée"), + ] + ], + "de": [ + (re.compile("\\b%s\\." % x[0], re.IGNORECASE), x[1]) + for x in [ + ("fr", "frau"), + ("dr", "doktor"), + ("st", "sankt"), + ("co", "firma"), + ("jr", "junior"), + ] + ], + "pt": [ + (re.compile("\\b%s\\." % x[0], re.IGNORECASE), x[1]) + for x in [ + ("sra", "senhora"), + ("sr", "senhor"), + ("dr", "doutor"), + ("dra", "doutora"), + ("st", "santo"), + ("co", "companhia"), + ("jr", "júnior"), + ("ltd", "limitada"), + ] + ], + "it": [ + (re.compile("\\b%s\\." % x[0], re.IGNORECASE), x[1]) + for x in [ + # ("sig.ra", "signora"), + ("sig", "signore"), + ("dr", "dottore"), + ("st", "santo"), + ("co", "compagnia"), + ("jr", "junior"), + ("ltd", "limitata"), + ] + ], + "pl": [ + (re.compile("\\b%s\\." % x[0], re.IGNORECASE), x[1]) + for x in [ + ("p", "pani"), + ("m", "pan"), + ("dr", "doktor"), + ("sw", "święty"), + ("jr", "junior"), + ] + ], + "ar": [ + (re.compile("\\b%s\\." % x[0], re.IGNORECASE), x[1]) + for x in [ + # There are not many common abbreviations in Arabic as in English. + ] + ], + "zh": [ + (re.compile("\\b%s\\." % x[0], re.IGNORECASE), x[1]) + for x in [ + # Chinese doesn't typically use abbreviations in the same way as Latin-based scripts. + ] + ], + "cs": [ + (re.compile("\\b%s\\." % x[0], re.IGNORECASE), x[1]) + for x in [ + ("dr", "doktor"), # doctor + ("ing", "inženýr"), # engineer + ("p", "pan"), # Could also map to pani for woman but no easy way to do it + # Other abbreviations would be specialized and not as common. + ] + ], + "ru": [ + (re.compile("\\b%s\\b" % x[0], re.IGNORECASE), x[1]) + for x in [ + ("г-жа", "госпожа"), # Mrs. + ("г-н", "господин"), # Mr. + ("д-р", "доктор"), # doctor + # Other abbreviations are less common or specialized. + ] + ], + "nl": [ + (re.compile("\\b%s\\." % x[0], re.IGNORECASE), x[1]) + for x in [ + ("dhr", "de heer"), # Mr. + ("mevr", "mevrouw"), # Mrs. + ("dr", "dokter"), # doctor + ("jhr", "jonkheer"), # young lord or nobleman + # Dutch uses more abbreviations, but these are the most common ones. + ] + ], + "tr": [ + (re.compile("\\b%s\\." % x[0], re.IGNORECASE), x[1]) + for x in [ + ("b", "bay"), # Mr. + ("byk", "büyük"), # büyük + ("dr", "doktor"), # doctor + # Add other Turkish abbreviations here if needed. + ] + ], + "hu": [ + (re.compile("\\b%s\\." % x[0], re.IGNORECASE), x[1]) + for x in [ + ("dr", "doktor"), # doctor + ("b", "bácsi"), # Mr. + ("nőv", "nővér"), # nurse + # Add other Hungarian abbreviations here if needed. + ] + ], + "ko": [ + (re.compile("\\b%s\\." % x[0], re.IGNORECASE), x[1]) + for x in [ + # Korean doesn't typically use abbreviations in the same way as Latin-based scripts. + ] + ], + "ja": [ + (re.compile("\\b%s\\b" % x[0]), x[1]) + for x in [ + ("氏", "さん"), # Mr. + ("夫人", "おんなのひと"), # Mrs. + ("博士", "はかせ"), # Doctor or PhD + ("株", "株式会社"), # Corporation + ("有", "有限会社"), # Limited company + ("大学", "だいがく"), # University + ("先生", "せんせい"), # Teacher/Professor/Master + ("君", "くん") # Used at the end of boys' names to express familiarity or affection. + ] + ], +} + + +def expand_abbreviations_multilingual(text, lang="en"): + for regex, replacement in _abbreviations[lang]: + text = re.sub(regex, replacement, text) + return text + + +_symbols_multilingual = { + "en": [ + (re.compile(r"%s" % re.escape(x[0]), re.IGNORECASE), x[1]) + for x in [ + ("&", " and "), + ("@", " at "), + ("%", " percent "), + ("#", " hash "), + ("$", " dollar "), + ("£", " pound "), + ("°", " degree "), + ] + ], + "es": [ + (re.compile(r"%s" % re.escape(x[0]), re.IGNORECASE), x[1]) + for x in [ + ("&", " y "), + ("@", " arroba "), + ("%", " por ciento "), + ("#", " numeral "), + ("$", " dolar "), + ("£", " libra "), + ("°", " grados "), + ] + ], + "fr": [ + (re.compile(r"%s" % re.escape(x[0]), re.IGNORECASE), x[1]) + for x in [ + ("&", " et "), + ("@", " arobase "), + ("%", " pour cent "), + ("#", " dièse "), + ("$", " dollar "), + ("£", " livre "), + ("°", " degrés "), + ] + ], + "de": [ + (re.compile(r"%s" % re.escape(x[0]), re.IGNORECASE), x[1]) + for x in [ + ("&", " und "), + ("@", " at "), + ("%", " prozent "), + ("#", " raute "), + ("$", " dollar "), + ("£", " pfund "), + ("°", " grad "), + ] + ], + "pt": [ + (re.compile(r"%s" % re.escape(x[0]), re.IGNORECASE), x[1]) + for x in [ + ("&", " e "), + ("@", " arroba "), + ("%", " por cento "), + ("#", " cardinal "), + ("$", " dólar "), + ("£", " libra "), + ("°", " graus "), + ] + ], + "it": [ + (re.compile(r"%s" % re.escape(x[0]), re.IGNORECASE), x[1]) + for x in [ + ("&", " e "), + ("@", " chiocciola "), + ("%", " per cento "), + ("#", " cancelletto "), + ("$", " dollaro "), + ("£", " sterlina "), + ("°", " gradi "), + ] + ], + "pl": [ + (re.compile(r"%s" % re.escape(x[0]), re.IGNORECASE), x[1]) + for x in [ + ("&", " i "), + ("@", " małpa "), + ("%", " procent "), + ("#", " krzyżyk "), + ("$", " dolar "), + ("£", " funt "), + ("°", " stopnie "), + ] + ], + "ar": [ + # Arabic + (re.compile(r"%s" % re.escape(x[0]), re.IGNORECASE), x[1]) + for x in [ + ("&", " و "), + ("@", " على "), + ("%", " في المئة "), + ("#", " رقم "), + ("$", " دولار "), + ("£", " جنيه "), + ("°", " درجة "), + ] + ], + "zh": [ + # Chinese + (re.compile(r"%s" % re.escape(x[0]), re.IGNORECASE), x[1]) + for x in [ + ("&", " 和 "), + ("@", " 在 "), + ("%", " 百分之 "), + ("#", " 号 "), + ("$", " 美元 "), + ("£", " 英镑 "), + ("°", " 度 "), + ] + ], + "cs": [ + # Czech + (re.compile(r"%s" % re.escape(x[0]), re.IGNORECASE), x[1]) + for x in [ + ("&", " a "), + ("@", " na "), + ("%", " procento "), + ("#", " křížek "), + ("$", " dolar "), + ("£", " libra "), + ("°", " stupně "), + ] + ], + "ru": [ + # Russian + (re.compile(r"%s" % re.escape(x[0]), re.IGNORECASE), x[1]) + for x in [ + ("&", " и "), + ("@", " собака "), + ("%", " процентов "), + ("#", " номер "), + ("$", " доллар "), + ("£", " фунт "), + ("°", " градус "), + ] + ], + "nl": [ + # Dutch + (re.compile(r"%s" % re.escape(x[0]), re.IGNORECASE), x[1]) + for x in [ + ("&", " en "), + ("@", " bij "), + ("%", " procent "), + ("#", " hekje "), + ("$", " dollar "), + ("£", " pond "), + ("°", " graden "), + ] + ], + "tr": [ + (re.compile(r"%s" % re.escape(x[0]), re.IGNORECASE), x[1]) + for x in [ + ("&", " ve "), + ("@", " at "), + ("%", " yüzde "), + ("#", " diyez "), + ("$", " dolar "), + ("£", " sterlin "), + ("°", " derece "), + ] + ], + "hu": [ + (re.compile(r"%s" % re.escape(x[0]), re.IGNORECASE), x[1]) + for x in [ + ("&", " és "), + ("@", " kukac "), + ("%", " százalék "), + ("#", " kettőskereszt "), + ("$", " dollár "), + ("£", " font "), + ("°", " fok "), + ] + ], + "ko": [ + # Korean + (re.compile(r"%s" % re.escape(x[0]), re.IGNORECASE), x[1]) + for x in [ + ("&", " 그리고 "), + ("@", " 에 "), + ("%", " 퍼센트 "), + ("#", " 번호 "), + ("$", " 달러 "), + ("£", " 파운드 "), + ("°", " 도 "), + ] + ], + "ja": [ + (re.compile(r"%s" % re.escape(x[0])), x[1]) + for x in [ + ("&", " と "), + ("@", " アットマーク "), + ("%", " パーセント "), + ("#", " ナンバー "), + ("$", " ドル "), + ("£", " ポンド "), + ("°", " 度"), + ] + ], +} + + +def expand_symbols_multilingual(text, lang="en"): + for regex, replacement in _symbols_multilingual[lang]: + text = re.sub(regex, replacement, text) + text = text.replace(" ", " ") # Ensure there are no double spaces + return text.strip() + + +_ordinal_re = { + "en": re.compile(r"([0-9]+)(st|nd|rd|th)"), + "es": re.compile(r"([0-9]+)(º|ª|er|o|a|os|as)"), + "fr": re.compile(r"([0-9]+)(º|ª|er|re|e|ème)"), + "de": re.compile(r"([0-9]+)(st|nd|rd|th|º|ª|\.(?=\s|$))"), + "pt": re.compile(r"([0-9]+)(º|ª|o|a|os|as)"), + "it": re.compile(r"([0-9]+)(º|°|ª|o|a|i|e)"), + "pl": re.compile(r"([0-9]+)(º|ª|st|nd|rd|th)"), + "ar": re.compile(r"([0-9]+)(ون|ين|ث|ر|ى)"), + "cs": re.compile(r"([0-9]+)\.(?=\s|$)"), # In Czech, a dot is often used after the number to indicate ordinals. + "ru": re.compile(r"([0-9]+)(-й|-я|-е|-ое|-ье|-го)"), + "nl": re.compile(r"([0-9]+)(de|ste|e)"), + "tr": re.compile(r"([0-9]+)(\.|inci|nci|uncu|üncü|\.)"), + "hu": re.compile(r"([0-9]+)(\.|adik|edik|odik|edik|ödik|ödike|ik)"), + "ko": re.compile(r"([0-9]+)(번째|번|차|째)"), + "ja": re.compile(r"([0-9]+)(番|回|つ|目|等|位)") +} +_number_re = re.compile(r"[0-9]+") +_currency_re = { + "USD": re.compile(r"((\$[0-9\.\,]*[0-9]+)|([0-9\.\,]*[0-9]+\$))"), + "GBP": re.compile(r"((£[0-9\.\,]*[0-9]+)|([0-9\.\,]*[0-9]+£))"), + "EUR": re.compile(r"(([0-9\.\,]*[0-9]+€)|((€[0-9\.\,]*[0-9]+)))"), +} + +_comma_number_re = re.compile(r"\b\d{1,3}(,\d{3})*(\.\d+)?\b") +_dot_number_re = re.compile(r"\b\d{1,3}(.\d{3})*(\,\d+)?\b") +_decimal_number_re = re.compile(r"([0-9]+[.,][0-9]+)") + + +def _remove_commas(m): + text = m.group(0) + if "," in text: + text = text.replace(",", "") + return text + + +def _remove_dots(m): + text = m.group(0) + if "." in text: + text = text.replace(".", "") + return text + + +def _expand_decimal_point(m, lang="en"): + amount = m.group(1).replace(",", ".") + return num2words(float(amount), lang=lang if lang != "cs" else "cz") + + +def _expand_currency(m, lang="en", currency="USD"): + amount = float((re.sub(r"[^\d.]", "", m.group(0).replace(",", ".")))) + full_amount = num2words(amount, to="currency", currency=currency, lang=lang if lang != "cs" else "cz") + + and_equivalents = { + "en": ", ", + "es": " con ", + "fr": " et ", + "de": " und ", + "pt": " e ", + "it": " e ", + "pl": ", ", + "cs": ", ", + "ru": ", ", + "nl": ", ", + "ar": ", ", + "tr": ", ", + "hu": ", ", + "ko": ", ", + } + + if amount.is_integer(): + last_and = full_amount.rfind(and_equivalents[lang]) + if last_and != -1: + full_amount = full_amount[:last_and] + + return full_amount + + +def _expand_ordinal(m, lang="en"): + return num2words(int(m.group(1)), ordinal=True, lang=lang if lang != "cs" else "cz") + + +def _expand_number(m, lang="en"): + return num2words(int(m.group(0)), lang=lang if lang != "cs" else "cz") + + +def expand_numbers_multilingual(text, lang="en"): + if lang == "zh": + text = zh_num2words()(text) + else: + if lang in ["en", "ru"]: + text = re.sub(_comma_number_re, _remove_commas, text) + else: + text = re.sub(_dot_number_re, _remove_dots, text) + try: + text = re.sub(_currency_re["GBP"], lambda m: _expand_currency(m, lang, "GBP"), text) + text = re.sub(_currency_re["USD"], lambda m: _expand_currency(m, lang, "USD"), text) + text = re.sub(_currency_re["EUR"], lambda m: _expand_currency(m, lang, "EUR"), text) + except: + pass + if lang != "tr": + text = re.sub(_decimal_number_re, lambda m: _expand_decimal_point(m, lang), text) + text = re.sub(_ordinal_re[lang], lambda m: _expand_ordinal(m, lang), text) + text = re.sub(_number_re, lambda m: _expand_number(m, lang), text) + return text + + +def lowercase(text): + return text.lower() + + +def collapse_whitespace(text): + return re.sub(_whitespace_re, " ", text) + + +def multilingual_cleaners(text, lang): + text = text.replace('"', "") + if lang == "tr": + text = text.replace("İ", "i") + text = text.replace("Ö", "ö") + text = text.replace("Ü", "ü") + text = lowercase(text) + text = expand_numbers_multilingual(text, lang) + text = expand_abbreviations_multilingual(text, lang) + text = expand_symbols_multilingual(text, lang=lang) + text = collapse_whitespace(text) + return text + + +def basic_cleaners(text): + """Basic pipeline that lowercases and collapses whitespace without transliteration.""" + text = lowercase(text) + text = collapse_whitespace(text) + return text + + +def chinese_transliterate(text): + return "".join( + [p[0] for p in pypinyin.pinyin(text, style=pypinyin.Style.TONE3, heteronym=False, neutral_tone_with_five=True)] + ) + + +def japanese_cleaners(text, katsu): + text = katsu.romaji(text) + text = lowercase(text) + return text + + +def korean_transliterate(text): + r = Transliter(academic) + return r.translit(text) + + +DEFAULT_VOCAB_FILE = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../data/tokenizer.json") + + +class VoiceBpeTokenizer: + def __init__(self, vocab_file=None): + self.tokenizer = None + if vocab_file is not None: + self.tokenizer = Tokenizer.from_file(vocab_file) + self.char_limits = { + "en": 250, + "de": 253, + "fr": 273, + "es": 239, + "it": 213, + "pt": 203, + "pl": 224, + "zh": 82, + "ar": 166, + "cs": 186, + "ru": 182, + "nl": 251, + "tr": 226, + "ja": 71, + "hu": 224, + "ko": 95, + } + + @cached_property + def katsu(self): + import cutlet + + return cutlet.Cutlet() + + def check_input_length(self, txt, lang): + lang = lang.split("-")[0] # remove the region + limit = self.char_limits.get(lang, 250) + if len(txt) > limit: + print( + f"[!] Warning: The text length exceeds the character limit of {limit} for language '{lang}', this might cause truncated audio." + ) + + def preprocess_text(self, txt, lang): + if lang in {"ar", "cs", "de", "en", "es", "fr", "hu", "it", "nl", "pl", "pt", "ru", "tr", "zh", "ko"}: + txt = multilingual_cleaners(txt, lang) + if lang == "zh": + txt = chinese_transliterate(txt) + if lang == "ko": + txt = korean_transliterate(txt) + elif lang == "ja": + txt = japanese_cleaners(txt, self.katsu) + elif lang == "hi": + # @manmay will implement this + txt = basic_cleaners(txt) + else: + raise NotImplementedError(f"Language '{lang}' is not supported.") + return txt + + def encode(self, txt, lang): + lang = lang.split("-")[0] # remove the region + self.check_input_length(txt, lang) + txt = self.preprocess_text(txt, lang) + lang = "zh-cn" if lang == "zh" else lang + txt = f"[{lang}]{txt}" + txt = txt.replace(" ", "[SPACE]") + return self.tokenizer.encode(txt).ids + + def decode(self, seq): + if isinstance(seq, torch.Tensor): + seq = seq.cpu().numpy() + txt = self.tokenizer.decode(seq, skip_special_tokens=False).replace(" ", "") + txt = txt.replace("[SPACE]", " ") + txt = txt.replace("[STOP]", "") + txt = txt.replace("[UNK]", "") + return txt + + def __len__(self): + return self.tokenizer.get_vocab_size() + + def get_number_tokens(self): + return max(self.tokenizer.get_vocab().values()) + 1 + + +def test_expand_numbers_multilingual(): + test_cases = [ + # English + ("In 12.5 seconds.", "In twelve point five seconds.", "en"), + ("There were 50 soldiers.", "There were fifty soldiers.", "en"), + ("This is a 1st test", "This is a first test", "en"), + ("That will be $20 sir.", "That will be twenty dollars sir.", "en"), + ("That will be 20€ sir.", "That will be twenty euro sir.", "en"), + ("That will be 20.15€ sir.", "That will be twenty euro, fifteen cents sir.", "en"), + ("That's 100,000.5.", "That's one hundred thousand point five.", "en"), + # French + ("En 12,5 secondes.", "En douze virgule cinq secondes.", "fr"), + ("Il y avait 50 soldats.", "Il y avait cinquante soldats.", "fr"), + ("Ceci est un 1er test", "Ceci est un premier test", "fr"), + ("Cela vous fera $20 monsieur.", "Cela vous fera vingt dollars monsieur.", "fr"), + ("Cela vous fera 20€ monsieur.", "Cela vous fera vingt euros monsieur.", "fr"), + ("Cela vous fera 20,15€ monsieur.", "Cela vous fera vingt euros et quinze centimes monsieur.", "fr"), + ("Ce sera 100.000,5.", "Ce sera cent mille virgule cinq.", "fr"), + # German + ("In 12,5 Sekunden.", "In zwölf Komma fünf Sekunden.", "de"), + ("Es gab 50 Soldaten.", "Es gab fünfzig Soldaten.", "de"), + ("Dies ist ein 1. Test", "Dies ist ein erste Test", "de"), # Issue with gender + ("Das macht $20 Herr.", "Das macht zwanzig Dollar Herr.", "de"), + ("Das macht 20€ Herr.", "Das macht zwanzig Euro Herr.", "de"), + ("Das macht 20,15€ Herr.", "Das macht zwanzig Euro und fünfzehn Cent Herr.", "de"), + # Spanish + ("En 12,5 segundos.", "En doce punto cinco segundos.", "es"), + ("Había 50 soldados.", "Había cincuenta soldados.", "es"), + ("Este es un 1er test", "Este es un primero test", "es"), + ("Eso le costará $20 señor.", "Eso le costará veinte dólares señor.", "es"), + ("Eso le costará 20€ señor.", "Eso le costará veinte euros señor.", "es"), + ("Eso le costará 20,15€ señor.", "Eso le costará veinte euros con quince céntimos señor.", "es"), + # Italian + ("In 12,5 secondi.", "In dodici virgola cinque secondi.", "it"), + ("C'erano 50 soldati.", "C'erano cinquanta soldati.", "it"), + ("Questo è un 1° test", "Questo è un primo test", "it"), + ("Ti costerà $20 signore.", "Ti costerà venti dollari signore.", "it"), + ("Ti costerà 20€ signore.", "Ti costerà venti euro signore.", "it"), + ("Ti costerà 20,15€ signore.", "Ti costerà venti euro e quindici centesimi signore.", "it"), + # Portuguese + ("Em 12,5 segundos.", "Em doze vírgula cinco segundos.", "pt"), + ("Havia 50 soldados.", "Havia cinquenta soldados.", "pt"), + ("Este é um 1º teste", "Este é um primeiro teste", "pt"), + ("Isso custará $20 senhor.", "Isso custará vinte dólares senhor.", "pt"), + ("Isso custará 20€ senhor.", "Isso custará vinte euros senhor.", "pt"), + ( + "Isso custará 20,15€ senhor.", + "Isso custará vinte euros e quinze cêntimos senhor.", + "pt", + ), # "cêntimos" should be "centavos" num2words issue + # Polish + ("W 12,5 sekundy.", "W dwanaście przecinek pięć sekundy.", "pl"), + ("Było 50 żołnierzy.", "Było pięćdziesiąt żołnierzy.", "pl"), + ("To będzie kosztować 20€ panie.", "To będzie kosztować dwadzieścia euro panie.", "pl"), + ("To będzie kosztować 20,15€ panie.", "To będzie kosztować dwadzieścia euro, piętnaście centów panie.", "pl"), + # Arabic + ("في الـ 12,5 ثانية.", "في الـ اثنا عشر , خمسون ثانية.", "ar"), + ("كان هناك 50 جنديًا.", "كان هناك خمسون جنديًا.", "ar"), + # ("ستكون النتيجة $20 يا سيد.", 'ستكون النتيجة عشرون دولار يا سيد.', 'ar'), # $ and € are mising from num2words + # ("ستكون النتيجة 20€ يا سيد.", 'ستكون النتيجة عشرون يورو يا سيد.', 'ar'), + # Czech + ("Za 12,5 vteřiny.", "Za dvanáct celá pět vteřiny.", "cs"), + ("Bylo tam 50 vojáků.", "Bylo tam padesát vojáků.", "cs"), + ("To bude stát 20€ pane.", "To bude stát dvacet euro pane.", "cs"), + ("To bude 20.15€ pane.", "To bude dvacet euro, patnáct centů pane.", "cs"), + # Russian + ("Через 12.5 секунды.", "Через двенадцать запятая пять секунды.", "ru"), + ("Там было 50 солдат.", "Там было пятьдесят солдат.", "ru"), + ("Это будет 20.15€ сэр.", "Это будет двадцать евро, пятнадцать центов сэр.", "ru"), + ("Это будет стоить 20€ господин.", "Это будет стоить двадцать евро господин.", "ru"), + # Dutch + ("In 12,5 seconden.", "In twaalf komma vijf seconden.", "nl"), + ("Er waren 50 soldaten.", "Er waren vijftig soldaten.", "nl"), + ("Dat wordt dan $20 meneer.", "Dat wordt dan twintig dollar meneer.", "nl"), + ("Dat wordt dan 20€ meneer.", "Dat wordt dan twintig euro meneer.", "nl"), + # Chinese (Simplified) + ("在12.5秒内", "在十二点五秒内", "zh"), + ("有50名士兵", "有五十名士兵", "zh"), + # ("那将是$20先生", '那将是二十美元先生', 'zh'), currency doesn't work + # ("那将是20€先生", '那将是二十欧元先生', 'zh'), + # Turkish + # ("12,5 saniye içinde.", 'On iki virgül beş saniye içinde.', 'tr'), # decimal doesn't work for TR + ("50 asker vardı.", "elli asker vardı.", "tr"), + ("Bu 1. test", "Bu birinci test", "tr"), + # ("Bu 100.000,5.", 'Bu yüz bin virgül beş.', 'tr'), + # Hungarian + ("12,5 másodperc alatt.", "tizenkettő egész öt tized másodperc alatt.", "hu"), + ("50 katona volt.", "ötven katona volt.", "hu"), + ("Ez az 1. teszt", "Ez az első teszt", "hu"), + # Korean + ("12.5 초 안에.", "십이 점 다섯 초 안에.", "ko"), + ("50 명의 병사가 있었다.", "오십 명의 병사가 있었다.", "ko"), + ("이것은 1 번째 테스트입니다", "이것은 첫 번째 테스트입니다", "ko"), + ] + for a, b, lang in test_cases: + out = expand_numbers_multilingual(a, lang=lang) + assert out == b, f"'{out}' vs '{b}'" + + +def test_abbreviations_multilingual(): + test_cases = [ + # English + ("Hello Mr. Smith.", "Hello mister Smith.", "en"), + ("Dr. Jones is here.", "doctor Jones is here.", "en"), + # Spanish + ("Hola Sr. Garcia.", "Hola señor Garcia.", "es"), + ("La Dra. Martinez es muy buena.", "La doctora Martinez es muy buena.", "es"), + # French + ("Bonjour Mr. Dupond.", "Bonjour monsieur Dupond.", "fr"), + ("Mme. Moreau est absente aujourd'hui.", "madame Moreau est absente aujourd'hui.", "fr"), + # German + ("Frau Dr. Müller ist sehr klug.", "Frau doktor Müller ist sehr klug.", "de"), + # Portuguese + ("Olá Sr. Silva.", "Olá senhor Silva.", "pt"), + ("Dra. Costa, você está disponível?", "doutora Costa, você está disponível?", "pt"), + # Italian + ("Buongiorno, Sig. Rossi.", "Buongiorno, signore Rossi.", "it"), + # ("Sig.ra Bianchi, posso aiutarti?", 'signora Bianchi, posso aiutarti?', 'it'), # Issue with matching that pattern + # Polish + ("Dzień dobry, P. Kowalski.", "Dzień dobry, pani Kowalski.", "pl"), + ("M. Nowak, czy mogę zadać pytanie?", "pan Nowak, czy mogę zadać pytanie?", "pl"), + # Czech + ("P. Novák", "pan Novák", "cs"), + ("Dr. Vojtěch", "doktor Vojtěch", "cs"), + # Dutch + ("Dhr. Jansen", "de heer Jansen", "nl"), + ("Mevr. de Vries", "mevrouw de Vries", "nl"), + # Russian + ("Здравствуйте Г-н Иванов.", "Здравствуйте господин Иванов.", "ru"), + ("Д-р Смирнов здесь, чтобы увидеть вас.", "доктор Смирнов здесь, чтобы увидеть вас.", "ru"), + # Turkish + ("Merhaba B. Yılmaz.", "Merhaba bay Yılmaz.", "tr"), + ("Dr. Ayşe burada.", "doktor Ayşe burada.", "tr"), + # Hungarian + ("Dr. Szabó itt van.", "doktor Szabó itt van.", "hu"), + ] + + for a, b, lang in test_cases: + out = expand_abbreviations_multilingual(a, lang=lang) + assert out == b, f"'{out}' vs '{b}'" + + +def test_symbols_multilingual(): + test_cases = [ + ("I have 14% battery", "I have 14 percent battery", "en"), + ("Te veo @ la fiesta", "Te veo arroba la fiesta", "es"), + ("J'ai 14° de fièvre", "J'ai 14 degrés de fièvre", "fr"), + ("Die Rechnung beträgt £ 20", "Die Rechnung beträgt pfund 20", "de"), + ("O meu email é ana&joao@gmail.com", "O meu email é ana e joao arroba gmail.com", "pt"), + ("linguaggio di programmazione C#", "linguaggio di programmazione C cancelletto", "it"), + ("Moja temperatura to 36.6°", "Moja temperatura to 36.6 stopnie", "pl"), + ("Mám 14% baterie", "Mám 14 procento baterie", "cs"), + ("Těším se na tebe @ party", "Těším se na tebe na party", "cs"), + ("У меня 14% заряда", "У меня 14 процентов заряда", "ru"), + ("Я буду @ дома", "Я буду собака дома", "ru"), + ("Ik heb 14% batterij", "Ik heb 14 procent batterij", "nl"), + ("Ik zie je @ het feest", "Ik zie je bij het feest", "nl"), + ("لدي 14% في البطارية", "لدي 14 في المئة في البطارية", "ar"), + ("我的电量为 14%", "我的电量为 14 百分之", "zh"), + ("Pilim %14 dolu.", "Pilim yüzde 14 dolu.", "tr"), + ("Az akkumulátorom töltöttsége 14%", "Az akkumulátorom töltöttsége 14 százalék", "hu"), + ("배터리 잔량이 14%입니다.", "배터리 잔량이 14 퍼센트입니다.", "ko"), + ] + + for a, b, lang in test_cases: + out = expand_symbols_multilingual(a, lang=lang) + assert out == b, f"'{out}' vs '{b}'" + + +if __name__ == "__main__": + test_expand_numbers_multilingual() + test_abbreviations_multilingual() + test_symbols_multilingual() diff --git a/system/gradio_pages/alltalk_about.py b/system/gradio_pages/alltalk_about.py new file mode 100644 index 0000000..2f6b3dd --- /dev/null +++ b/system/gradio_pages/alltalk_about.py @@ -0,0 +1,16 @@ +import gradio as gr + +def alltalk_about(): + about = """ + ### 🛠️ About this project + > AllTalk is a labour of love that has been developed, supported and sustained in my personal free time. As a solo enthusiast (not a business or team) my resources are inherently limited. This project has been one of my passions, but I must balance it with other commitments.

+ To manage AllTalk sustainably, I prioritize support requests based on their overall impact and the number of users affected. I encourage you to utilize the comprehensive documentation and engage with the AllTalk community discussion area. These resources often provide immediate answers and foster a supportive user network.

+ Should your inquiry extend beyond the documentation, especially if it concerns a bug or feature request, I assure you I’ll offer my best support as my schedule permits. However, please be prepared for varying response times, reflective of the personal dedication I bring to AllTalk. Your understanding and patience in this regard are greatly appreciated.

+ It's important to note that **I am not** the developer of any TTS models utilized by AllTalk, nor do I claim to be an expert on them, including understanding all their nuances, issues, and quirks. For specific TTS model concerns, I’ve provided links to the original developers in each TTS engines "Engine Information" section for direct assistance/research.

+ Thank you for your continued support and understanding. + + ### 💖 Showing Your Support + > If AllTalk has been helpful to you, consider showing your support through a donation on my **[Ko-fi page](https://ko-fi.com/erew123)**.

+ Your support is greatly appreciated and helps ensure the continued development and improvement of AllTalk. + """ + gr.Markdown(about) \ No newline at end of file diff --git a/system/gradio_pages/alltalk_diskspace.py b/system/gradio_pages/alltalk_diskspace.py new file mode 100644 index 0000000..a3fd22f --- /dev/null +++ b/system/gradio_pages/alltalk_diskspace.py @@ -0,0 +1,76 @@ +import os +from pathlib import Path +import gradio as gr + +# Define directories +this_dir = Path(__file__).parent.resolve() +main_dir = this_dir.parent.parent.resolve() + +# Folder paths +folders = { + "models": main_dir / "models", + "finetune": main_dir / "finetune", + "voices": main_dir / "voices", + "outputs": main_dir / "outputs", + "alltalk_environment": main_dir / "alltalk_environment" +} + +def get_folder_size_scandir(folder_path): + total_size = 0 + for entry in os.scandir(folder_path): + if entry.is_file(follow_symlinks=False): + try: + total_size += entry.stat(follow_symlinks=False).st_size + except OSError as e: + print(f"Error getting size for file {entry.path}: {e}") + elif entry.is_dir(follow_symlinks=False): + try: + total_size += get_folder_size_scandir(entry.path) + except OSError as e: + print(f"Error accessing directory {entry.path}: {e}") + return total_size + +def format_size(size): + # Convert size to a readable format + for unit in ['B', 'KB', 'MB', 'GB', 'TB']: + if size < 1024: + return f"{size:.2f} {unit}" + size /= 1024 + +def calculate_disk_space(): + sizes = {} + for name, path in folders.items(): + if path.exists(): + folder_size = get_folder_size_scandir(path) + # print(f"Folder: {name}, Total Size: {folder_size} bytes") + sizes[name] = format_size(folder_size) + else: + sizes[name] = "Not present" + + return sizes['models'], sizes['finetune'], sizes['voices'], sizes['outputs'], sizes['alltalk_environment'] + +def disk_space_page(): + # Define the layout + with gr.Tab("Disk Space Usage"): + with gr.Row(): + models_space = gr.Textbox(label="Models Folder", interactive=False) + finetune_space = gr.Textbox(label="Finetune Folder", interactive=False) + voices_space = gr.Textbox(label="Voices Folder", interactive=False) + outputs_space = gr.Textbox(label="Outputs Folder", interactive=False) + alltalk_env_space = gr.Textbox(label="Alltalk Python Environment", interactive=False) + + calculate_button = gr.Button("Calculate Disk Space Usage") + + # Define actions for buttons + calculate_button.click(calculate_disk_space, outputs=[models_space, finetune_space, voices_space, outputs_space, alltalk_env_space]) + +def get_disk_interface(): + return disk_space_page + +# Example usage in the main script +if __name__ == "__main__": + app = gr.Blocks() + with app: + disk_space_page() + + app.launch() diff --git a/system/gradio_pages/alltalk_documentation.py b/system/gradio_pages/alltalk_documentation.py new file mode 100644 index 0000000..fa62b8d --- /dev/null +++ b/system/gradio_pages/alltalk_documentation.py @@ -0,0 +1,1207 @@ +import gradio as gr + +def alltalk_documentation(): + low_vram = """ + The Low VRAM option is a crucial feature designed to enhance performance under constrained (VRAM) conditions, as TTS models **can** require 2GB-3GB of VRAM to run effectively. This feature strategically manages the relocation of the Text-to-Speech (TTS) model between your system's Random Access Memory (RAM) and VRAM, moving it between the two on the fly. Obviously, this is very useful for people who have smaller graphics cards or people who's LLM has filled their VRAM, but you want to keep up performance. + + When you don't have enough VRAM free after loading your LLM model into your VRAM (Normal Mode example below), you can see that with so little working space, your GPU will have to swap in and out bits of the TTS model, which causes horrible slowdown. + + Note An Nvidia Graphics card is required for the LowVRAM option to work, as you will just be using system RAM otherwise. + + ### How It Works + The Low VRAM mode intelligently relocates of the entire TTS model and stores the TTS model in your system RAM when not being used. When the TTS engine requires VRAM for processing, the entire model seamlessly moves into VRAM, causing your LLM to unload/displace some layers, ensuring optimal performance of the TTS engine. + + Post-TTS processing, the model moves back to system RAM, freeing up VRAM space for your Language Model (LLM) to load back in the missing layers. This adds about 1-2 seconds to both text generation by the LLM and the TTS engine. + + By transferring the entire model between RAM and VRAM, the Low VRAM option avoids model fragmentation, ensuring the TTS model remains cohesive and has all the working space it needs in your GPU, without having to just work on small bits of the TTS model at a time (which causes terrible slow down). + + This creates a TTS generation performance Boost for Low VRAM Users and is particularly beneficial for users with less than 2GB of free VRAM after loading their LLM, delivering a substantial 5-10x improvement in TTS generation speed. + """ + + silly_tavern_support = """ + #### Important note for Text-generation-webui users + You **HAVE** to disable **Enable TTS** within the Text-generation-webui AllTalk interface, otherwise Text-generation-webui will also generate TTS due to the way it sends out text. You can do this each time you start up Text-generation-webui or set it in the start-up settings at the top of this page. + + ### 🏰 Quick Tips + > Only change DeepSpeed, Low VRAM or Model one at a time. Wait for it to say Ready before changing something else.
+ > You can permanently change the AllTalk startup settings or DeepSpeed, Low VRAM and Model at the top of this page.
+ > Different AI models use quotes and asterisks differently, so you may need to change "Text not inside" depending on model.
+ > Add new voice samples to the voices folder. You can Finetune a model to make it sound even closer to the original sample.
+ > DeepSpeed will improve processing time to TTS to be 2-3x faster.
+ > Low VRAM can be very beneficial if you don't have much memory left after loading your LLM.
+ + #### 🏰 TTS Generation Methods in SilllyTavern + You have 2 types of audio generation options, Streaming and Standard. + + The Streaming Audio Generation method is designed for speed and is best suited for situations where you just want quick audio playback. This method, however, is limited to using just one voice per TTS generation request. This means a limitation of the Streaming method is the inability to utilize the AllTalk narrator function, making it a straightforward but less nuanced option. + + On the other hand, the Standard Audio Generation method provides a richer auditory experience. It's slightly slower than the Streaming method but compensates for this with its ability to split text into multiple voices. This functionality is particularly useful in scenarios where differentiating between character dialogues and narration can enhance the storytelling and delivery. The inclusion of the AllTalk narrator functionality in the Standard method allows for a more layered and immersive experience, making it ideal for content where depth and variety in voice narration add significant value. + + In summary, the choice between Streaming and Standard methods in AllTalk TTS depends on what you want. Streaming is great for quick and simple audio generation, while Standard is preferable for a more dynamic and engaging audio experience. + + **AllTalk TTS Generation Method:** + > Select between Standard and Streaming Audio Generation methods.
+ > This setting impacts the AllTalk narrator functionality. + + **Language Selection:** + > Select your preferred TTS generation language from the "Language" dropdown. + + **Model Switching:** + > Switch between different TTS models like API TTS, API Local, XTTSv2 Local, and optionally XTTSv2 FT if you have a finetuned model available.
+ > Fine-tuned model availability (XTTSv2 FT) will only show when a finetuned model is detected by AllTalk.
+ > See TTS Models/Methods for more information (though most people will want to stick with XTTSv2 Local).
+ + **DeepSpeed and Low VRAM Options:** + > Optimize performance with DeepSpeed and Low VRAM settings.
+ > DeepSpeed can offer a 2-3x performance boost on TTS generation. (Requires installation)
+ > See the relevant sections in this documentation for details.
+ + Changing model or DeepSpeed or Low VRAM **each** take about 15 seconds so you should only change one at a time and wait for `Ready` before changing the next setting. To set these options long term you can apply the settings at the top of this page. + + #### 🏰 AllTalk Narrator + Only available on the Standard Audo Generation method. + + **Narrator Voice Selection:** + > Allows users to choose different narrator voices.
+ > Access via the "Narrator Voice" dropdown. + + **AllTalk Narrator:** + > Toggle the AllTalk narrator feature.
+ > Access via "AT Narrator" dropdown with Enabled/Disabled options. + + **Text Outside Asterisks Handling:** + > Choose how text outside asterisks is interpreted (as Narrator or Character voice).
+ > Managed via the "Text Not Inside * or "" dropdown.
+ > Note, only available when the AllTalk Narrator is enabled.
+ + #### 🏰 Usage Notes: + > On startup of SillyTavern, it will pull your current settings from AllTalk (Current model, DeepSpeed status, Low VRAM status and Finetuned model availability).
+ > Enabling the narrator automatically unchecks certain checkboxes related to text handling.
+ > Changes in model or settings might trigger multiple requests to the server; patience is advised.
+ + #### 🏰 Troubleshooting: + > If experiencing issues, use the Reload button in SillyTavern's TTS extension to reinitialize the connection to AllTalk and check if AllTalk is started correctly. + """ + + updating_alltalk = """ + ### 🟪 Updating + + Maintaining the latest version of your setup ensures access to new features and improvements. Below are the steps to update your installation, whether you're using Text-Generation-webui or running as a Standalone Application. + +
+ UPDATING - Text-Generation-webui + + The update process closely mirrors the installation steps. Follow these to ensure your setup remains current:
+ + **Open a Command Prompt/Terminal** + > Navigate to your Text-Generation-webui folder with:
+ > `cd text-generation-webui` + + **Start the Python Environment** + > Activate the Python environment tailored for your operating system. Use the appropriate command from below based on your OS:
+ > **Windows**: `cmd_windows.bat`
+ > **Linux**: `./cmd_linux.sh`
+ > **macOS**: `cmd_macos.sh`
+ > **WSL (Windows Subsystem for Linux)**: `cmd_wsl.bat`
+ + > If you're unfamiliar with Python environments and wish to learn more, consider reviewing **Understanding Python Environments Simplified** in the Help section. + + **Navigate to the AllTalk TTS Folder** + > Move into your extensions and then the alltalk_tts directory:
+ > `cd extensions/alltalk_tts` + + **Update the Repository** + > Fetch the latest updates from the repository with:
+ > `git pull` + + **Install Updated Requirements** + > Depending on your machine's OS, install the required dependencies using pip:
+ > **Windows Machines**:
+ > `pip install -r system\\requirements\\requirements_textgen.txt`

+ > **Linux/Mac**:
+ > `pip install -r system/requirements/requirements_textgen.txt` + + **DeepSpeed Requirements** + > If Text-gen-webui is using a new version of PyTorch, you **may** need to uninstall and update your DeepSpeed version.
+ > Use AllTalks diagnostics or start-up menu to identify your version of PyTorch. +
+
+ +
+ UPDATING - Standalone Application +
+ + If you installed from a ZIP file, you cannot use a `git pull` to update, as noted in the Quick Setup instructions. + + For Standalone Application users, here's how to update your setup: + + 1. **Open a Command Prompt/Terminal**: + > Navigate to your AllTalk folder with
+ > `cd alltalk_tts` + + 2. **Access the Python Environment**: + > In a command prompt or terminal window, navigate to your `alltalk_tts` directory and start the Python environment:
+ > **Windows**
+ > `start_environment.bat`
+ > **Linux/macOS**
+ > `./start_environment.sh`
+ + > If you're unfamiliar with Python environments and wish to learn more, consider reviewing **Understanding Python Environments Simplified** in the Help section. + + 2. **Pull the Latest Updates**: + > Retrieve the latest changes from the repository with:
+ > `git pull` + + 3. **Install Updated Requirements**: + > Depending on your machine's OS, install the required dependencies using pip:
+ > **Windows Machines**
+ > `pip install -r system\\requirements\\requirements_standalone.txt`
+ > **Linux/Mac**
+ > `pip install -r system/requirements/requirements_standalone.txt` +
+
+ + ### 🟪 Resolving Update Issues + + If you encounter problems during or after an update, following these steps can help resolve the issue by refreshing your installation while preserving your data: + +
+ RESOLVING - Updates
+ + The process involves renaming your existing `alltalk_tts` directory, setting up a fresh instance, and then migrating your data: + + 1. **Rename Existing Directory**: + > First, rename your current `alltalk_tts` folder to keep it safe e.g. `alltalk_tts.old`. This preserves any existing data. + + 2. **Follow the Quick Setup instructions**: + > You will now follow the **Quick Setup** instructions, performing the `git clone https://github.com/erew123/alltalk_tts` to pull down a new copy of AllTalk and install the requirements. + + > If you're not familiar with Python environments, see **Understanding Python Environments Simplified** in the Help section for more info. + + 3. **Migrate Your Data**: + > **Before** starting the AllTalk, transfer the `models`, `voices`, `outputs` folders and also `confignew.json` from `alltalk_tts.old` to the new `alltalk_tts` directory. This action preserves your voice history and prevents the need to re-download the model. + + 4) **Launch AllTalk** + > You're now ready to launch AllTalk and check it works correctly. + + 6. **Final Step**: + > Once you've verified that everything is working as expected and you're satisfied with the setup, feel free to delete the `alltalk_tts.old` directory to free up space. + +
+ """ + + narrator_guides = """ + Messages intended for the Narrator should be enclosed in asterisks `*` and those for the character inside quotation marks `"`. However, AI systems often deviate from these rules, resulting in text that is neither in quotes nor asterisks. Sometimes, text may appear with only a single asterisk, and AI models may vary their formatting mid-conversation. For example, they might use asterisks initially and then switch to unmarked text. A properly formatted line should look like this: + + `"`Hey! I'm so excited to finally meet you. I've heard so many great things about you and I'm eager to pick your brain about computers.`"` `*`She walked across the room and picked up her cup of coffee`*` + + Most narrator/character systems switch voices upon encountering an asterisk or quotation marks, which is somewhat effective. AllTalk has undergone several revisions in its sentence splitting and identification methods. While some irregularities and AI deviations in message formatting are inevitable, any line beginning or ending with an asterisk should now be recognized as Narrator dialogue. Lines enclosed in double quotes are identified as Character dialogue. For any other text, you can choose how AllTalk handles it: whether it should be interpreted as Character or Narrator dialogue (most AI systems tend to lean more towards one format when generating text not enclosed in quotes or asterisks). + + ### 🟦 Narrator Types of Text + + The TTS engine identifies and processes three types of text when the Narrator function is `Enabled` or `Enabled (Silent)`: + + > **Narrated Text**: Text enclosed in asterisks `*Narrated text*`.
+ > **Character Text**: Text enclosed in double quotes `"Character text"`.
+ > **Text-Not-Inside**: Any text not enclosed in asterisks or double quotes. The handling of this text is customizable.
+ + ### 🟦 Settings for Narrated Text + + You can control how narrated text is handled with the following settings: + + > **Enabled**: Narrated text will be spoken by the TTS engine using the narrator voice.
+ > **Enabled (Silent)**: Narrated text is recognized but not spoken. This is useful for adding silent annotations or actions within the text.
+ > **Disabled**: Narrated text will be spoken using the character voice instead of the narrator voice. The narrator engine is turned off, and the Text-Not-Inside setting does not apply.
+ + Please note, if you set `Enabled` or `Enabled (silent)` as the APi defaults, then all text will go into the narrator function unless `disabled` is sent as part of the TTS generation request. + + ### 🟦 Settings for Text-Not-Inside + + You can customize how text-not-inside is handled with these options: + + > **"Character"**: Text-not-inside is treated as character dialogue and will be spoken.
+ > ***Narrator***: Text-not-inside is treated as narration and will be spoken.
+ > **Silent**: Text-not-inside is recognized but not spoken.
+ + ### 🟦 Examples and Variations + + Here are some examples to illustrate how different settings affect the text processing: + + > **Example 1**:
+ **Narrator Enabled, Text-Not-Inside as Character**

+ > ***Narrated text*** will be spoken as narration.
+ > **"Character text"** will be spoken as character dialogue.
+ > **Any other** text will be spoken as character dialogue.
+ + > **Example 2**:
+ **Narrator Disabled**

+ > ***Narrated text*** will be spoken using the character voice.
+ > **"Character text"** will be spoken as character dialogue.
+ > **Any other** text will be spoken as character dialogue.
+ + > **Example 3**:
+ **Narrator Enabled (Silent), Text-Not-Inside as Silent**

+ > ***Narrated text*** will be recognized but not spoken.
+ > **"Character text"** will be spoken as character dialogue.
+ > **Any other** text will be recognized but not spoken.
+ + ### 🟦 Potential Confusions and Clarifications + + **Silent Options Impact** + + > **Narrator Enabled (Silent)**: This setting allows for silent annotations within your text. Users can add notes or actions in the narrative without them being spoken.
+ > **Text-Not-Inside as Silent**: This setting enables users to include text that is recognized by the TTS engine but remains unspoken, useful for adding context or instructions within the script that are not meant to be vocalized.
+ + **Combining Settings** + + > **Full Silence**: If both Narrator is set to `Enabled (Silent)` and Text-Not-Inside is set to `Silent`, **only character text** in quotes will be spoken.
+ > **Partial Silence**: You can configure the settings to have only certain parts of the text spoken while others are silent. For example, you might want all narration silent and only character dialogue spoken, or vice versa.
+ + ### 🟦 Short Story Example + + >*Once upon a time in a small village, there was a young girl named Lily.*
+ Lily woke up early every morning.
+ "I love exploring the forest near my home."
+ * Lily said. One day, she found a mysterious key lying on the ground.*
+ "This key looks old and rusty, but it seems to sparkle in the sunlight,"
+ She thought. Lily decided she would keep the key safe until she could discover its purpose.
+ *Curious and excited, she decided to find out what the key opened.*
+ + ### Short Story Explanation + + **Narrated Text** (Asterisks surrounding the text):
+ > *Once upon a time in a small village, there was a young girl named Lily.*
+ > * Lily said. One day, she found a mysterious key lying on the ground.*
+ > *Curious and excited, she decided to find out what the key opened.*

+ > These lines are enclosed in asterisks and represent the narration. If Narrator was set to `Enabled (Silent)` these lines would not be spoken. + + **Character Text** (Double Quotes surrounding the text):
+ > "I love exploring the forest near my home."
+ > "This key looks old and rusty, but it seems to sparkle in the sunlight."

+ > These lines are enclosed in double quotes and represent the character’s spoken dialogue or thoughts. + + **Text-Not-Inside** (Neither Asterisks nor Quotes):
+ > Lily woke up early every morning.
+ > She thought. Lily decided she would keep the key safe until she could discover its purpose.

+ > These lines are not enclosed in asterisks or quotes and will be handled according to the `Text-Not-Inside` settings. If set to `Text-not-inside` > `Silent` these lines would not be spoken. + + """ + + rvc_guides = """ + RVC (Retrieval-based Voice Conversion) enhances TTS by replicating voice characteristics for characters or narrators, adding depth to synthesized speech. It functions as a TTS-to-TTS pipeline and can be used with any TTS engine/model. However, the closer the original TTS generation is to the voice you want to use with RVC, the better the result will be. Therefore, it is recommended to use a voice cloning TTS engine like Coqui XTTS with voice samples for optimal performance. + + ### 🟩 Voice Model Files + + Voice models should be stored in the `/models/rvc_voices/{subfolder}` directory in their own subfolder. Typically, a voice model includes a PTH file and potentially an index file. If an index file is present, AllTalk will automatically select and use it. If multiple index files are found, none will be used, and a message will be output to the console. You can find places on the internet to download pre-generated RVC voice models e.g. https://voice-models.com/ and huggingface etc. + + ### 🟩 Purpose of the Index File + + The index file helps improve the quality of the generated audio by providing a reference during the conversion process. The FAISS index enables faster and more accurate retrieval of voice characteristics, leading to more natural and high-quality voice synthesis. The training data size setting determines how much of the index is used, impacting both quality and computation time. By indexing a larger number of data points, the conversion process can produce audio that closely matches the desired voice characteristics. + + ### 🟩 RVC Settings Page + """ + rvc_guides2 = """ + ### Default Character Voice Model + > This setting allows you to select the voice model used for character conversion. If "Disabled" is selected, RVC will not be applied to character voices. This option is used only if RVC is enabled and no other voice is specified in the API request. Selecting a specific model will apply RVC to all character dialogues, potentially making them sound more natural and lifelike. + + ### Default Narrator Voice Model + > This setting allows you to select the voice model used for narrator conversion. If "Disabled" is selected, RVC will not be applied to the narrator voice. This option is used only if RVC is enabled and no other voice is specified in the API request. Applying RVC to the narrator can enhance the overall narrative experience by providing a consistent and engaging voice. + + ### Index Influence Ratio + > Sets the influence exerted by the index file on the final output. A higher value increases the impact of the index, potentially enhancing detail but also increasing the risk of artifacts. Fine-tuning this setting helps achieve the desired balance between audio detail and artifact prevention. + + ### Pitch + > This slider sets the pitch of the audio output. Increasing the value raises the pitch, while decreasing the value lowers it. Adjusting the pitch can help match the voice to specific character traits or emotional states, enhancing the overall storytelling experience. + + ### Volume Envelope + > This setting substitutes or blends with the volume envelope of the output. A ratio closer to 1 means the output envelope is more heavily employed. This can help maintain the natural dynamics of the voice, making it sound more realistic and less robotic. + + ### Protect Voiceless Consonants/Breath Sounds + > Prevents artifacts in voiceless consonants and breath sounds. Higher values (up to 0.5) provide stronger protection but might affect indexing. This setting helps maintain the clarity and intelligibility of speech, especially in parts of the audio where voiceless consonants and breath sounds are present. + + ### AutoTune + > Enables or disables auto-tune for the generated audio. Recommended for singing conversions to ensure the output remains in tune. Auto-tune can correct pitch discrepancies and produce a more polished and harmonious sound. + """ + rvc_guides3 = """ + ### Filter Radius + > If the number is greater than or equal to three, employing median filtering on the collected tone results has the potential to decrease respiration. This setting helps reduce noise and unwanted sounds in the audio, leading to cleaner and more professional-sounding output. + + ### Training Data Size (AllTalk Specific) + > Determines the number of training data points used to train the FAISS index. Increasing the size may improve the quality of the output but can also increase computation time. Different index files have different sizes. This setting limits the maximum amount of the index used. For example, if an index file has 70,000 points and you set the limit to 50,000, only 50,000 points will be used. A higher training data size generally results in better audio quality because more data points contribute to a more accurate voice conversion. + + ### Embedder Model + > Select between different models for learning speaker embedding. "hubert" and "contentvec" are the available options.

+ **hubert**: A model that focuses on capturing the phonetic and linguistic content of the voice, which is useful for a wide range of voice conversion tasks.
+ **contentvec**: A model that captures more detailed voice characteristics and nuances, potentially offering higher fidelity in voice conversion but may require more computational resources. + + ### Split Audio + > Splits the audio into chunks for inference to obtain better results in some cases. This can improve the quality of conversion, especially for longer audio inputs, by ensuring more consistent and accurate processing across the entire length of the audio. + + ### Pitch Extraction Algorithm + > Choose the algorithm used for extracting the pitch (F0) during audio conversion. The available options include:

+ **crepe**: Provides high accuracy in pitch detection and is robust against various types of noise.
+ **crepe-tiny**: A smaller, faster version of crepe with slightly reduced accuracy.
+ **dio**: A fast pitch extraction algorithm that is less accurate than crepe but suitable for real-time applications.
+ **fcpe**: Focuses on precise pitch extraction, often used for high-fidelity voice conversion.
+ **harvest**: Known for producing smooth and natural pitch contours, suitable for music and singing voice conversion.
+ **hybrid[rmvpe+fcpe]**: Combines the strengths of rmvpe and fcpe to achieve a balance of accuracy and performance.
+ **pm**: A robust algorithm that works well in various conditions, offering a balance of speed and accuracy.
+ **rmvpe**: Recommended for most cases due to its balance of accuracy and performance, especially in TTS applications.
+ """ + + + + deepspeed_guides = """ + ### 🔵🟢 DeepSpeed Installation Options + **DeepSpeed requires an Nvidia Graphics card** + + --- + ## Documentation yet to be updated. In a Nutshell though.... + > AllTalk **Standalone** setups **(Windows & Linux)** will install DeepSpeed **automatically**. You can stop reading here and you dont have to do anything. + + > **Windows** users, please see instructions in `atsetup.bat` + + > **Linux users**, please look here https://github.com/erew123/alltalk_tts/releases/tag/DeepSpeed-14.2-Linux + --- + ### Legacy manual build of DeepSpeed Instructions + #### 🔵 Linux Installation + DeepSpeed requires access to the **Nvidia CUDA Development Toolkit** to compile on a Linux system. It's important to note that this toolkit is distinct and unrealted to your graphics card driver or the CUDA version the Python environment uses. + +
+ Linux DeepSpeed - Text-generation-webui + + ### DeepSpeed Installation for Text generation webUI + + 1. **Nvidia CUDA Development Toolkit Installation**: + - The toolkit is crucial for DeepSpeed to compile/build for your version of Linux and requires around 3GB's of disk space. + - Install using your package manager **(Recommended)** e.g. **CUDA Toolkit 11.8** or download directly from [Nvidia CUDA Toolkit Archive](https://developer.nvidia.com/cuda-toolkit-archive) (choose 11.8 or 12.1 for Linux). + + 2. **Open a Terminal Console**: + - After Nvidia CUDA Development Toolkit installation, access your terminal console. + + 3. **Install libaio-dev**: + - Use your Linux distribution's package manager. + + - - `sudo apt install libaio-dev` for Debian-based systems + - `sudo yum install libaio-devel` for RPM-based systems. + + 4. **Navigate to Text generation webUI Folder**: + - Change directory to your Text generation webUI folder with `cd text-generation-webui`. + + 5. **Activate Text generation webUI Custom Conda Environment**: + - Run `./cmd_linux.sh` to start the environment.

+ + > If you're unfamiliar with Python environments and wish to learn more, consider reviewing **Understanding Python Environments Simplified** in the Help section. + + 6. **Set `CUDA_HOME` Environment Variable**: + - DeepSpeed locates the Nvidia toolkit using the `CUDA_HOME` environment variable. + - You will only set this temporarily as Text generation webUI sets up its own CUDA_HOME environment each time you use `./cmd_linux.sh` or `./start_linux.sh` + + 7. **Temporarily Configuring `CUDA_HOME`**: + - When the Text generation webUI Python environment is active **(step 5)**, set `CUDA_HOME`. + + - - `export CUDA_HOME=/usr/local/cuda` + - `export PATH=${CUDA_HOME}/bin:${PATH}` + - `export LD_LIBRARY_PATH=${CUDA_HOME}/lib64:$LD_LIBRARY_PATH` + + - You can confirm the path is set correctly and working by running the command `nvcc --version` should confirm `Cuda compilation tools, release 11.8.`. + - Incorrect path settings may lead to errors. If you encounter path issues or receive errors like `[Errno 2] No such file or directory` when you run the next step, confirm the path correctness or adjust as necessary. + + 8. **DeepSpeed Installation**: + - Install DeepSpeed using `pip install deepspeed`. + + 9. **Troubleshooting**: + - Troubleshooting steps for DeepSpeed installation can be located down below. + - **NOTE**: You **DO NOT** need to set Text-generation-webUI's **--deepspeed** setting for AllTalk to be able to use DeepSpeed. These are two completely separate things and incorrectly setting that on Text-generation-webUI may cause other complications. +
+
+ Linux DeepSpeed - Standalone Installation + + ### DeepSpeed Installation for Standalone AllTalk + + 1. **Nvidia CUDA Development Toolkit Installation**: + - The toolkit is crucial for DeepSpeed to compile/build for your version of Linux and requires around 3GB's of disk space. + - Install using your package manager **(Recommended)** e.g. **CUDA Toolkit 11.8** or download directly from [Nvidia CUDA Toolkit Archive](https://developer.nvidia.com/cuda-toolkit-archive) (choose 11.8 or 12.1 for Linux). + + 2. **Open a Terminal Console**: + - After Nvidia CUDA Development Toolkit installation, access your terminal console. + + 3. **Install libaio-dev**: + - Use your Linux distribution's package manager. + + - - `sudo apt install libaio-dev` for Debian-based systems + - `sudo yum install libaio-devel` for RPM-based systems. + + 4. **Navigate to AllTalk TTS Folder**: + - Change directory to your AllTalk TTS folder with `cd alltalk_tts`. + + 5. **Activate AllTalk Custom Conda Environment**: + - Run `./start_environment.sh` to start the AllTalk Python environment. + - This command will start the custom Python environment that was installed with `./atsetup.sh`.

+ + > If you're unfamiliar with Python environments and wish to learn more, consider reviewing **Understanding Python Environments Simplified** in the Help section. + + 6. **Set `CUDA_HOME` Environment Variable**: + - The DeepSpeed installation routine locates the Nvidia toolkit using the `CUDA_HOME` environment variable. This can be set temporarily for a session or permanently, depending on other requirements you may have for other Python/System environments. + - For temporary use, proceed to **step 8**. For a permanent solution, see [Conda's manual on setting environment variables](https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#set-env-vars). + + 7. **(Optional) Permanent `CUDA_HOME` Setup**: + - If you choose to set `CUDA_HOME` permanently, follow the instructions in the provided Conda manual link above. + + 8. **Configuring `CUDA_HOME`**: + - When your Python environment is active **(step 5)**, set `CUDA_HOME`. + + - - `export CUDA_HOME=/usr/local/cuda` + - `export PATH=${CUDA_HOME}/bin:${PATH}` + - `export LD_LIBRARY_PATH=${CUDA_HOME}/lib64:$LD_LIBRARY_PATH` + + - You can confirm the path is set correctly and working by running the command `nvcc --version` should confirm `Cuda compilation tools, release 11.8.`. + - Incorrect path settings may lead to errors. If you encounter path issues or receive errors like `[Errno 2] No such file or directory` when you run the next step, confirm the path correctness or adjust as necessary. + + 9. **DeepSpeed Installation**: + - Install DeepSpeed using `pip install deepspeed`. + + 10. **Starting AllTalk TTS WebUI**: + - Launch the AllTalk TTS interface with `./start_alltalk.sh` and enable DeepSpeed. + + ### Troubleshooting + + - If setting `CUDA_HOME` results in path duplication errors (e.g., `.../bin/bin/nvcc`), you can correct this by unsetting `CUDA_HOME` with `unset CUDA_HOME` and then adding the correct path to your system's PATH variable. + - Always verify paths and compatibility with other CUDA-dependent applications to avoid conflicts. + - If you have multiple versions of the Nvidia CUDA Development Toolkit installed, you will have to specify the version number in step 8 for the CUDA_HOME path. + - If it became necessary to uninstall DeepSpeed, you can do so by start the Python enviroment and then running `pip uninstall deepspeed`

+ +
+ + #### 🟢 Windows Installation + You have 2x options for how to setup DeepSpeed on Windows. Pre-compiled wheel files for specific Python, CUDA and Pytorch builds, or manually compiling DeepSpeed. + +
+ Windows DeepSpeed - Pre-Compiled Wheels (Quick and Easy) + + ### DeepSpeed Installation with Pre-compiled Wheels + + 1. **Introduction to Pre-compiled Wheels**: + - The `atsetup.bat` utility simplifies the installation of DeepSpeed by automatically downloading and installing pre-compiled wheel files. These files are tailored for **specific** versions of Python, CUDA, and PyTorch, ensuring compatibility with both the **Standalone Installation** and a standard build of **Text-generation-webui**. + + 2. **Manual Installation of Pre-compiled Wheels**: + - If needed, pre-compiled DeepSpeed wheel files that I have built are available on the [Releases Page](https://github.com/erew123/alltalk_tts/releases). You can manually install or uninstall these wheels using the following commands: + - Installation: `pip install {deep-speed-wheel-file-name-here}` + - Uninstallation: `pip uninstall deepspeed` + + 3. **Using `atsetup.bat` for Simplified Management**: + - For those running the Standalone Installation or a standard build of Text-generation-webui, the `atsetup.bat` utility offers the simplest and most efficient way to manage DeepSpeed installations on Windows. + +
+ +
+ Windows DeepSpeed - Manual Compilation + + ### Manual DeepSpeed Wheel Compilation + + 1. **Preparation for Manual Compilation**: + - Manual compilation of DeepSpeed wheels is an advanced process that requires: + - **1-2 hours** of your time for initial setup and compilation. + - **6-10GB** of disk space on your computer. + - A solid technical understanding of Windows environments and Python. + + 2. **Understanding Wheel Compatibility**: + - A compiled DeepSpeed wheel is uniquely tied to the specific versions of Python, PyTorch, and CUDA used during its compilation. If any of these versions are changed, you will need to compile a new DeepSpeed wheel to ensure compatibility. + + 3. **Compiling DeepSpee Resources**: + - Myself and [@S95Sedan](https://github.com/S95Sedan) have worked to simplify the compilation process. [@S95Sedan](https://github.com/S95Sedan) has notably improved the process for later versions of DeepSpeed, ensuring ease of build on Windows. + - Because [@S95Sedan](https://github.com/S95Sedan) is now maintaining the instructions for compiling DeepSpeed on Windows, please visit [@S95Sedan](https://github.com/S95Sedan)'s
[DeepSpeed GitHub page](https://github.com/S95Sedan/Deepspeed-Windows). + +
+ """ + + help_with_problems = """ + ### 🆘 Support Requests, Troubleshooting & Feature requests + I'm thrilled to see the enthusiasm and engagement with AllTalk! Your feedback and questions are invaluable, helping to make this project even better. To ensure everyone gets the help they need efficiently, please consider the following before submitting a support request: + + **Consult the Documentation:** A comprehensive guide and FAQ sections (below) are available to help you navigate AllTalk. Many common questions and troubleshooting steps are covered here. + + **Search Past Discussions:** Your issue or question might already have been addressed in the discussions area or [closed issues](https://github.com/erew123/alltalk_tts/issues?q=is%3Aissue+is%3Aclosed). Please use the search function to see if there's an existing solution or advice that applies to your situation. + + **Bug Reports:** If you've encountered what you believe is a bug, please first check the [Updates & Bug Fixes List](https://github.com/erew123/alltalk_tts/issues/25) to see if it's a known issue or one that's already been resolved. If not, I encourage you to report it by raising a bug report in the [Issues section](https://github.com/erew123/alltalk_tts/issues), providing as much detail as possible to help identify and fix the issue. + + **Feature Requests:** The current Feature request list can be [found here](https://github.com/erew123/alltalk_tts/discussions/74). I love hearing your ideas for new features! While I can't promise to implement every suggestion, I do consider all feedback carefully. Please share your thoughts in the [Discussions area](https://github.com/erew123/alltalk_tts/discussions) or via a Feature Request in the [Issues section](https://github.com/erew123/alltalk_tts/issues). + + + ### 🟨 Help with problems + + ####      🔄 **Minor updates/bug fixes list** can be found [here](https://github.com/erew123/alltalk_tts/issues/25) + +
+ 🟨 How to make a diagnostics report file
+ + If you are on a Windows machine or a Linux machine, you should be able to use the `atsetup.bat` or `./atsetup.sh` utility to create a diagnositcs file. If you are unable to use the `atsetup` utility, please follow the instructions below. + +
+ Manually making a diagnostics report file
+ + 1) Open a command prompt window and start the Python environment. Depending on your setup (Text-generation-webui or Standalone AllTalk), the steps to start the Python environment vary:
+ + - **For Text-generation-webui Users**: + - Navigate to the Text-generation-webui directory: + - `cd text-generation-webui` + - Start the Python environment suitable for your OS: + - Windows: `cmd_windows.bat` + - Linux: `./cmd_linux.sh` + - macOS: `cmd_macos.sh` + - WSL (Windows Subsystem for Linux): `cmd_wsl.bat` + - Move into the AllTalk directory: + - `cd extensions/alltalk_tts` + + - **For Standalone AllTalk Users**: + - Navigate to the `alltalk_tts` folder: + - `cd alltalk_tts` + - Start the Python environment: + - Windows: `start_environment.bat` + - Linux: `./start_environment.sh`

+ + > If you're unfamiliar with Python environments and wish to learn more, consider reviewing **Understanding Python Environments Simplified** in the Help section. + + 2) Run the diagnostics and select the requirements file name you installed AllTalk with:
+ - `python diagnostics.py` + + 3) You will have an on screen output showing your environment setttings, file versions request vs whats installed and details of your graphics card (if Nvidia). This will also create a file called `diagnostics.log` in the `alltalk_tts` folder, that you can upload if you need to create a support ticket on here.

+ + ![image](https://github.com/erew123/alltalk_tts/assets/35898566/81b9a6e1-c54b-4da0-b85d-3c6fde566d6a) +

+ + ### Installation and Setup Issues + +
+ 🟨 Understanding Python Environments Simplified
+ + Think of Python environments like different rooms in your house, each designed for a specific purpose. Just as you wouldn't cook in the bathroom or sleep in the kitchen, different Python applications need their own "spaces" or environments because they have unique requirements. Sometimes, these requirements can clash with those of other applications (imagine trying to cook a meal in a bathroom!). To avoid this, you can create separate Python environments. + + #### Why Separate Environments? + Separate environments, like separate rooms, keep everything organized and prevent conflicts. For instance, one Python application might need a specific version of a library or dependency, while another requires a different version. Just as you wouldn't store kitchen utensils in the bathroom, you wouldn't want these conflicting requirements to interfere with each other. Each environment is tailored and customized for its application, ensuring it has everything it needs without disrupting others. + + #### How It Works in Practice: + + **Standalone AllTalk Installation:** When you install AllTalk standalone, it's akin to adding a new room to your house specifically designed for your AllTalk activities. The setup process, using the atsetup utility, constructs this custom "room" (Python environment `alltalk_environment`) with all the necessary tools and furnishings (libraries and dependencies) that AllTalk needs to function smoothly, without meddling with the rest of your "house" (computer system). The AllTalk environment is started each time you run `start_alltalk` or `start_environment` within the AllTalk folder. + + **Text-generation-webui Installation:** Similarly, installing Text-generation-webui is like setting up another specialized room. Upon installation, it automatically creates its own tailored environment, equipped with everything required for text generation, ensuring a seamless and conflict-free operation. The Text-generation-webui environment is started each time you run `start_*your-os-version*` or `cmd_*your-os-version*` within the Text-generation-webui folder. + + #### Managing Environments: + Just as you might renovate a room or bring in new furniture, you can also update or modify Python environments as needed. Tools like Conda or venv make it easy to manage these environments, allowing you to create, duplicate, activate, or delete them much like how you might manage different rooms in your house for comfort and functionality. + + Once you're in the right environment, by activating it, installing or updating dependencies (the tools and furniture of your Python application) is straightforward. Using pip, a package installer for Python, you can easily add what you need. For example, to install all required dependencies listed in a requirements.txt file, you'd use: + + `pip install -r requirements.txt` + + This command tells pip to read the list of required packages and versions from the requirements.txt file and install them in the current environment, ensuring your application has everything it needs to operate. It's like having a shopping list for outfitting a room and ensuring you have all the right items delivered and set up. + + Remember, just as it's important to use the right tools for tasks in different rooms of your house, it's crucial to manage your Python environments and dependencies properly to ensure your applications run as intended. + + #### How do I know if I am in a Python environment?: + When a Python environment starts up, it changes the command prompt to show the Python environment that it currently running within that terminal/console. + + ![image](https://github.com/erew123/screenshots/blob/main/pythonenvironment.jpg) +
+ +
+ 🟨 Windows & Python requirements for compiling packages (ERROR: Could not build wheels for TTS)
+ + `ERROR: Microsoft Visual C++ 14.0 or greater is required` or `ERROR: Could not build wheels for TTS.` or `ModuleNotFoundError: No module named 'TTS` + + Python requires that you install C++ development tools on Windows. This is detailed on the [Python site here](https://wiki.python.org/moin/WindowsCompilers). You would need to install `MSVCv142 - VS 2019 C++ x64/x86 build tools` and `Windows 10/11 SDK` from the C++ Build tools section. + + You can get hold of the **Community** edition [here](https://visualstudio.microsoft.com/downloads/) the during installation, selecting `C++ Build tools` and then `MSVCv142 - VS 2019 C++ x64/x86 build tools` and `Windows 10/11 SDK`. + + ![image](https://github.com/erew123/screenshots/raw/main/pythonrequirementswindows.jpg) + +
+
+ 🟨 Standalone Install - start_{youros}.xx opens and closes instantly and AllTalk doesnt start
+ + This is more than likely caused by having a space ` ` in your folder path e.g. `c:\program files\alltalk_tts`. In this circumstance you would be best moving the folder to a path without a space e.g. `c:\myfiles\alltalk_tts`. You would have to delete the `alltalk_environment` folder and `start_alltalk.bat` or `start_alltalk.sh` and then re-run `atsetup` to re-create the environment and startup files. +
+ +
+ 🟨 Text-geneneration-webui & Stable-Diffusion Plugin - Load Order & stripped text
+ + The Stable Diffusion plugin for Text-generation-webui **strips out** some of the text, which is passed to Stable Diffusion for image/scene generation. Because this text is stripped, its important to consider the load order of the plugins to get the desired result you want. Lets assume the AI has just generated the following message `*He walks into the room with a smile on his face and says* Hello how are you?`. Depending on the load order will change what text reaches AllTalk for generation e.g. + + **SD Plugin loaded before AllTalk** - Only `Hi how are you?` is sent to AllTalk, with the `*He walks into the room with a smile on his face and says*` being sent over to SD for image generation. Narration of the scene is not possible.

+ **AllTalk loaded before SD Plugin** - `*He walks into the room with a smile on his face and says* Hello how are you?` is sent to AllTalk with the `*He walks into the room with a smile on his face and says*` being sent over to SD for image generation.

+ The load order can be changed within Text-generation-webui's `settings.yaml` file or `cmd_flags.txt` (depending on how you are managing your extensions).

+
+ +
+ 🟨 I think AllTalks requirements file has installed something another extension doesn't like
+ + Ive paid very close attention to **not** impact what Text-generation-webui is requesting on a factory install. This is one of the requirements of submitting an extension to Text-generation-webui. If you want to look at a comparison of a factory fresh text-generation-webui installed packages (with cuda 12.1, though AllTalk's requirements were set on cuda 11.8) you can find that comparison [here](https://github.com/erew123/alltalk_tts/issues/23). This comparison shows that AllTalk is requesting the same package version numbers as Text-generation-webui or even lower version numbers (meaning AllTalk will not update them to a later version). What other extensions do, I cant really account for that. + + I will note that the TTS engine downgrades Pandas data validator to 1.5.3 though its unlikely to cause any issues. You can upgrade it back to text-generation-webui default (december 2023) with `pip install pandas==2.1.4` when inside of the python environment. I have noticed no ill effects from it being a lower or higher version, as far as AllTalk goes. This is also the same behaviour as the Coqui_tts extension that comes with Text-generation-webui. + + Other people are reporting issues with extensions not starting with errors about Pydantic e.g. ```pydantic.errors.PydanticImportError: BaseSettings` has been moved to the pydantic-settings package. See https://docs.pydantic.dev/2.5/migration/#basesettings-has-moved-to-pydantic-settings for more details.``` + + Im not sure if the Pydantic version has been recently updated by the Text-generation-webui installer, but this is nothing to do with AllTalk. The other extension you are having an issue with, need to be updated to work with Pydantic 2.5.x. AllTalk was updated in mid december to work with 2.5.x. I am not specifically condoning doing this, as it may have other knock on effects, but within the text-gen Python environment, you can use `pip install pydantic==2.5.0` or `pip install pydantic==1.10.13` to change the version of Pydantic installed. +
+
+ 🟨 I am having problems getting AllTalk to start after changing settings or making a custom setup/model setup.
+ + I would suggest following [Problems Updating](https://github.com/erew123/alltalk_tts#-problems-updating) and if you still have issues after that, you can raise an issue [here](https://github.com/erew123/alltalk_tts/issues) +
+ + ### Networking and Access Issues + +
+ 🟨 I cannot access AllTalk from another machine on my Network
+ + You will need to change the IP address within AllTalk's settings from being 127.0.0.1, which only allows access from the local machine its installed on. To do this, please see [Changing AllTalks IP address & Accessing AllTalk over your Network](https://github.com/erew123/alltalk_tts/tree/main?tab=readme-ov-file#-changing-alltalks-ip-address--accessing-alltalk-over-your-network) at the top of this page. + + You may also need to allow access through your firewall or Antivirus package to AllTalk. +
+ +
+ 🟨 I am running a Headless system and need to change the IP Address manually as I cannot reach the config page
+ + To do this you can edit the `confignew.json` file within the `alltalk_tts` folder. You would look for `"ip_address": "127.0.0.1",` and change the `127.0.0.1` to your chosen IP address,then save the file and start AllTalk.

+ + When doing this, be careful not to impact the formatting of the JSON file. Worst case, you can re-download a fresh copy of `confignew.json` from this website and that will put you back to a factory setting. +
+ + ### Configuration and Usage Issues +
+ 🟨 I activated DeepSpeed in the settings page, but I didnt install DeepSpeed yet and now I have issues starting up
+ + You can either follow the [Problems Updating](https://github.com/erew123/alltalk_tts#-problems-updating) and fresh install your config. Or you can edit the `confignew.json` file within the `alltalk_tts` folder. You would look for '"deepspeed_activate": true,' and change the word true to false `"deepspeed_activate": false,' ,then save the file and try starting again. + + If you want to use DeepSpeed, you need an Nvidia Graphics card and to install DeepSpeed on your system. Instructions are [here](https://github.com/erew123/alltalk_tts#-deepspeed-installation-options) +
+ +
+ 🟨 I am having problems updating/some other issue where it wont start up/Im sure this is a bug
+ + Please see [Problems Updating](https://github.com/erew123/alltalk_tts#-problems-updating). If that doesnt help you can raise an ticket [here](https://github.com/erew123/alltalk_tts/issues). It would be handy to have any log files from the console where your error is being shown. I can only losely support custom built Python environments and give general pointers. Please create a `diagnostics.log` report file to submit with a support request. + + Also, is your text-generation-webui up to date? [instructions here](https://github.com/oobabooga/text-generation-webui?tab=readme-ov-file#how-to-install) +
+ +
+ 🟨 Error Details: HTTPConnectionPool(host='null', port=80): Max retries exceeded with url: /api/tts-generate............ etc + + If you get this error, the gradio window you have open has lost connection to the AllTalk backend. This is most likely caused by AllTalk being restarted in the background and you not opening the gradio window again. Simply refresh the gradio web page and it will be connected back in and working. +
+
+ +
+ 🟨 I see some red "asyncio" messages
+ + As far as I am aware, these are to do with the chrome browser the gradio text-generation-webui in some way. I raised an issue about this on the text-generation-webui [here](https://github.com/oobabooga/text-generation-webui/issues/4788) where you can see that AllTalk is not loaded and the messages persist. Either way, this is more a warning than an actual issue, so shouldnt affect any functionality of either AllTalk or text-generation-webui, they are more just an annoyance. +
+ + ### Startup, Performance and Compatibility Issues + +
+ 🟨 Understanding the AllTalk start-up screen
+ + The AllTalk start-up screen provides various bits of information about the detected Python environment and errors. + + ![image](https://github.com/erew123/screenshots/raw/main/alltalkstartup.jpg) + + **Config file check**
+ - Sometimes I need to add/remove something to your existing configuration file settings. Obviously, I don’t want to impact your existing settings, however any new features may need these settings to be created before AllTalk starts up. Ive added extra code that checks `alltalk_tts/system/config/at_configupdate.json` and `alltalk_tts/system/config/at_configdowngrade.json`, either adding or removing items to your configuration as necessary. If a change is made, you will be notified and a backup of the previous configuration file will be created in the `alltalk_tts` folder. + + **AllTalk startup Mode**
+ - informational. This will state if AllTalk has detected it is running as part of Text-generation-webui or as a Standalone Application. + + **WAV file deletion**
+ - If you have set deletion of old generated WAV files, this will state the time frame after which they are purged. + + **DeepSpeed version**
+ - What version of DeepSpeed is installed/detected. This will **not** tell you if the version of DeepSpeed is compiled for your Python, PyTorch or CUDA version. Its important to remember that DeepSpeed has to be compiled for the exact version of Python, PyTorch and CUDA that you are using, so please ensure you have the correct DeepSpeed version installed if necessary. + + **Model is available**
+ - AllTalk is checking if your model files exist. This is not a validity check of the actual model files, they can still be corrupted. If files are missing, AllTalk will attempt to download them from Huggingface, however, if Huggingface has an outage/issue or your internet connection has issues, its possible corrupted or incomplete files will be downloaded. Please read `RuntimeError: PytorchStreamReader failed reading zip archive: failed finding central directory` if you need to confirm your model files are ok. + + **Current Python Version**
+ - Informational. Literally tells you the version of Python running in your Python environment. + + **Current PyTorch Version**
+ - Informational. Tell tells you the version of **PyTorch** running in your Python environment, however if you have an Nvidia card, you should be running a CUDA based version of Pytorch. This is indicated with a `+cXXX` after the PyTorch version e.g. `2.2.2+cu121` would be PyTorch version 2.2.2 with CUDA 12.1 extensions. If you don’t have the PyTorch CUDA extensions installed, but you do have an Nvidia card, you may need to re-install PyTorch. + + **Current CUDA Version**
+ - Informational. This is linked to the Current PyTorch Version, as detailed above. + + **Current TTS Version**
+ - Informational. The current version of the TTS engine that is running. + + **AllTalk Github updated**
+ - As long as you have an internet connection, this will tell you last time AllTalk was updated on Github. It is checking the [commit list](https://github.com/erew123/alltalk_tts/commits/main/) to see when the last commit was made. As such, this could be simply a documentation update, a bug fix or new features. Its simply there as a guide to let you know the last time something was changed on AllTalk's Github. + + **TTS Subprocess**
+ - When AllTalk reaches this stage, the subprocess that loads in the AI model is starting. This is most likely where an error could occur with loading the TTS model, just after the documentation message. + + **AllTalk Settings & Documentation: http ://x.x.x.x**
+ - The link where you can reach AllTalks built in settings and documentation page. The TTS model will be loading immediately after this is displayed. +

+
+ +
+ 🟨 AllTalk is only loading into CPU, but I have an Nvidia GPU so it should be loading into CUDA
+ + This is caused by Pytorch (Torch) not having the CUDA extensions installed (You can check by running the diagnostics). Typically this happens (on Standalone installations) because when the setup routine goes to install Pytorch with CUDA, it looks in the PIP cache and if a previous application has downloaded a version of Pytorch that **doesn't** have CUDA extensions, the PIP installer doesnt recognise this fact and just uses the cached version for installation. To resolve this: + + 1) On the `atsetup` utility, on the `Standalone menu` select to `Purge the PIP cache`. This will remove cached packages from the PIP cache, meaning it will have to download fresh copies. + 2) As we need to force the upgrade to the Python environment, the easiest way to do this will be to use `atsetup` to `Delete AllTalk's custom Python environment`. This means it will have to rebuild the Python environment. **Note**, you may have to run this step twice, as it has to exit the current Python environment, then you have to re-load `atsetup` and select `Delete AllTalk's custom Python environment` again. + 3) You can now use `atsetup` to `Install AllTalk as a Standalone Application` which will download fresh copies of everything and re-install the Python environment. + 4) Once this is done you can check if CUDA is now working with the diagnostics or starting AllTalk and checking the model loads into CUDA. + +
+ + +
+ 🟨 RuntimeError: PytorchStreamReader failed reading zip archive: failed finding central directory
+ + This error message is caused by the model being corrupted or damaged in some way. This error can occur if Huggingface, where the model is downloaded from, have an error (when the model is downloaded) or potentailly internet issues occuring while the model is downloaded on first start-up. + + ``` + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + RuntimeError: PytorchStreamReader failed reading zip archive: failed finding central directory + + ERROR: Application startup failed. Exiting. + [AllTalk Startup] Warning TTS Subprocess has NOT started up yet, Will keep trying for 120 seconds maximum. Please wait. + ``` + + To resolve this, first look in your `alltalk_tts/models/xttsv2_2.0.2` (or whichever) model folder and confirm that the file sizes are correct. + + ![image](https://github.com/erew123/screenshots/raw/main/modelsfiles.jpg) + + You can delete one or more suspect files and a factory fresh copy of that file or files will be downloaded on next start-up of AllTalk. + +
+ +
+ 🟨 RuntimeError: Found no NVIDIA driver on your system.
+ + This error message is caused by DeepSpeed being enabled when you do not have a Nvidia GPU. To resolve this, edit `confignew.json` and change `"deepspeed_activate": true,` to `"deepspeed_activate": false,` then restart AllTalk. + + ``` + File "C:\alltalk_tts\alltalk_environment\env\Lib\site-packages\torch\cuda\__init__.py", line 302, in _lazy_init + torch._C._cuda_init() + RuntimeError: Found no NVIDIA driver on your system. Please check that you have an NVIDIA GPU and installed a driver from http://www.nvidia.com/Download/index.aspx + + ERROR: Application startup failed. Exiting. + [AllTalk Startup] Warning TTS Subprocess has NOT started up yet, Will keep trying for 120 seconds maximum. Please wait. + ``` + +
+ +
+ 🟨 raise RuntimeError("PyTorch version mismatch! DeepSpeed ops were compiled and installed.
+ + This error message is caused by having DeepSpeed enabled, but you have a version of DeepSpeed installed that was compiled for a different version of Python, PyTorch or CUDA (or any mix of those). You will need to start your Python environment and run `pip uninstall deepspeed` to remove DeepSpeed from your Python environment and then install the correct version of DeepSpeed. + + ``` + raise RuntimeError("PyTorch version mismatch! DeepSpeed ops were compiled and installed + RuntimeError: PyTorch version mismatch! DeepSpeed ops were compiled and installed with a different version than what is being used at runtime. Please re-install DeepSpeed or switch torch versions. Install torch version=2.1, Runtime torch version=2.2 + ``` + +
+ +
+ 🟨 Warning TTS Subprocess has NOT started up yet, Will keep trying for 120 seconds maximum. Please wait. It times out after 120 seconds.
+ When the subprocess is starting 2x things are occurring:

+ + **A)** Its trying to load the voice model into your graphics card VRAM (assuming you have a Nvidia Graphics card, otherwise its your system RAM)
+ **B)** Its trying to start up the mini-webserver and send the "ready" signal back to the main process. + + Before giving other possibilities a go, some people with **old machines** are finding their startup times are **very** slow 2-3 minutes. Ive extended the allowed time within the script from 1 minute to 2 minutes. **If you have an older machine** and wish to try extending this further, you can do so by editing `script.py` and changing `startup_wait_time = 120` (120 seconds, aka 2 minutes) at the top of the script.py file, to a larger value e.g `startup_wait_time = 240` (240 seconds aka 4 minutes). + + **Note:** If you need to create a support ticket, please create a `diagnostics.log` report file to submit with a support request. Details on doing this are above. + + Other possibilities for this issue are: + + 1) You are starting AllTalk in both your `CMD FLAG.txt` and `settings.yaml` file. The `CMD FLAG.txt` you would have manually edited and the `settings.yaml` is the one you change and save in the `session` tab of text-generation-webui and you can `Save UI defaults to settings.yaml`. Please only have one of those two starting up AllTalk. + + 2) You are not starting text-generation-webui with its normal Python environment. Please start it with start_{your OS version} as detailed [here](https://github.com/oobabooga/text-generation-webui#how-to-install) (`start_windows.bat`,`./start_linux.sh`, `start_macos.sh` or `start_wsl.bat`) OR (`cmd_windows.bat`, `./cmd_linux.sh`, `cmd_macos.sh` or `cmd_wsl.bat` and then `python server.py`). + + 3) You have installed the wrong version of DeepSpeed on your system, for the wrong version of Python/Text-generation-webui. You can go to your text-generation-webui folder in a terminal/command prompt and run the correct cmd version for your OS e.g. (`cmd_windows.bat`, `./cmd_linux.sh`, `cmd_macos.sh` or `cmd_wsl.bat`) and then you can type `pip uninstall deepspeed` then try loading it again. If that works, please see here for the correct instructions for installing DeepSpeed [here](https://github.com/erew123/alltalk_tts#-deepspeed-installation-options). + + 4) You have an old version of text-generation-webui (pre Dec 2023) I have not tested on older versions of text-generation-webui, so cannot confirm viability on older versions. For instructions on updating the text-generation-webui, please look [here](https://github.com/oobabooga/text-generation-webui#how-to-install) (`update_linux.sh`, `update_windows.bat`, `update_macos.sh`, or `update_wsl.bat`). + + 5) You already have something running on port 7851 on your computer, so the mini-webserver cant start on that port. You can change this port number by editing the `confignew.json` file and changing `"port_number": "7851"` to `"port_number": "7602"` or any port number you wish that isn’t reserved. Only change the number and save the file, do not change the formatting of the document. This will at least discount that you have something else clashing on the same port number. + + 6) You have antivirus/firewalling that is blocking that port from being accessed. If you had to do something to allow text-generation-webui through your antivirus/firewall, you will have to do that for this too. + + 7) You have quite old graphics drivers and may need to update them. + + 8) Something within text-generation-webui is not playing nicely for some reason. You can go to your text-generation-webui folder in a terminal/command prompt and run the correct cmd version for your OS e.g. (`cmd_windows.bat`, `./cmd_linux.sh`, `cmd_macos.sh` or `cmd_wsl.bat`) and then you can type `python extensions\alltalk_tts\script.py` and see if AllTalk starts up correctly. If it does then something else is interfering. + + 9) Something else is already loaded into your VRAM or there is a crashed python process. Either check your task manager for erroneous Python processes or restart your machine and try again. + + 10) You are running DeepSpeed on a Linux machine and although you are starting with `./start_linux.sh` AllTalk is failing there on starting. This is because text-generation-webui will overwrite some environment variables when it loads its python environment. To see if this is the problem, from a terminal go into your text-generation-webui folder and `./cmd_linux.sh` then set your environment variable again e.g. `export CUDA_HOME=/usr/local/cuda` (this may vary depending on your OS, but this is the standard one for Linux, and assuming you have installed the CUDA toolkit), then `python server.py` and see if it starts up. If you want to edit the environment permanently you can do so, I have not managed to write full instructions yet, but here is the conda guide [here](https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#set-env-vars). + + 11) You have built yourself a custom Python environment and something is funky with it. This is very hard to diagnose as its not a standard environment. You may want to updating text-generation-webui and re installing its requirements file (whichever one you use that comes down with text-generation-webui). +
+ +
+ 🟨 I have multiple GPU's and I have problems running Finetuning
+ + Finetuning pulls in various other scripts and some of those scripts can have issues with multiple Nvidia GPU's being present. Until the people that created those other scripts fix up their code, there is a workaround to temporarily tell your system to only use the 1x of your Nvidia GPU's. To do this: + + - **Windows** - You will start the script with `set CUDA_VISIBLE_DEVICES=0 && python finetune.py`
+ After you have completed training, you can reset back with `set CUDA_VISIBLE_DEVICES=`
+ + - **Linux** - You will start the script with `CUDA_VISIBLE_DEVICES=0 python finetune.py`
+ After you have completed training, you can reset back with `unset CUDA_VISIBLE_DEVICES`
+ + Rebooting your system will also unset this. The setting is only applied temporarily. + + Depending on which of your Nvidia GPU's is the more powerful one, you can change the `0` to `1` or whichever of your GPU's is the most powerful. + +
+ +
+ 🟨 Firefox - Streaming Audio doesnt work on Firefox
+ + This is a long standing issue with Mozilla & Firefox and one I am unable to resolve as Mozilla have not resolved the issue with Firefox. The solution is to use another web browser if you want to use Streaming audio. For details of my prior invesitigation please look at this [ticket](https://github.com/erew123/alltalk_tts/issues/143) +
+ + ### Application Specific Issues + +
+ 🟨 SillyTavern - I changed my IP address and now SillyTavern wont connect with AllTalk
+ SillyTavern checks the IP address when loading extensions, saving the IP to its configuration only if the check succeeds. For whatever reason, SillyTavern's checks dont always allow changing its IP address a second time.

+ + To manually change the IP address: + + 1) Navigate to the SillyTavern Public folder located at `/sillytavern/public/`. + 2) Open the `settings.json` file. + 3) Look for the AllTalk section and find the `provider_endpoint` entry. + 3) Replace `localhost` with your desired IP address, for example, `192.168.1.64`. + + ![image](https://github.com/SillyTavern/SillyTavern/assets/35898566/144e4ac4-87dc-4a2b-8a73-39314abed1ca) +
+ + ### TTS Generation Issues & Questions + +
+ 🟨 XTTS - Does the XTTS AI Model Support Emotion Control or Singing?
+ + No, the XTTS AI model does not currently support direct control over emotions or singing capabilities. While XTTS infuses generated speech with a degree of emotional intonation based on the context of the text, users cannot explicitly control this aspect. It's worth noting that regenerating the same line of TTS may yield slightly different emotional inflections, but there is no way to directly control it with XTTS. +
+
+ 🟨 XTTS - Skips, repeats or pronunciation Issues
+ + Firstly, it's important to clarify that the development and maintenance of the XTTS AI models and core scripts are handled by [Coqui](https://docs.coqui.ai/en/latest/index.html), with additional scripts and libraries from entities like [huggingface](https://huggingface.co/docs/transformers/en/index) among many other Python scripts and libraries used by AllTalk. + + AllTalk is designed to be a straightforward interface that simplifies setup and interaction with AI TTS models like XTTS. Currently, AllTalk supports the XTTS model, with plans to include more models in the future. Please understand that the deep inner workings of XTTS, including reasons why it may skip, repeat, or mispronounce, along with 3rd party scripts and libraries utilized, are ultimately outside my control. + + Although I ensure the text processed through AllTalk is accurately relayed to the XTTS model speech generation process, and I have aimed to mitigate as many issues as much as possible; skips, repeats and bad pronounciation can still occur. + + Certain aspects I have not been able to investigate due to my own time limitations, are:
+ + - The impact of DeepSpeed on TTS quality. Is this more likely to cause skips or repetition? + - Comparative performance between different XTTS model versions (e.g., 2.0.3 vs. 2.0.2) regarding audio quality and consistency. + + **From my experience and anecdotally gained knowledge:**
+ + - Lower quality voice samples tend to produce more anomalies in generated speech. + - Model finetuning with high-quality voice samples significantly reduces such issues, enhancing overall speech quality. + - Unused/Excessive punctuation causes issues e.g. asterisks `*`, hashes `#`, brackets `(` `)` etc. Many of these AllTalk will filter out. + + So for example, the `female_01.wav` file that is provided with AllTalk is a studio quality voice sample, which the XTTS model was trained on. Typically you will find it unlikely that anomolies occur with TTS generation when using this voice sample. Hence good quality samples and finetuning, generally improve results with XTTS. + + If you wish to try out the XTTS version 2.0.3 model and see if it works better, you can download it from [here](https://huggingface.co/coqui/XTTS-v2/tree/v2.0.3), replacing all the files within your `/alltalk_tts/models/xttsv2_2.0.2` folder. This is on my list to both test version 2.0.3 more, but also build a more flexible TTS models downloader, that will not only accomdating other XTTS models, but also other TTS engines. If you try the XTTS version 2.0.3 model and gleen any insights, please let me know. +
+ """ + + finetuning_a_model = """ + ### ⚫ Finetuning a model + If you have a voice that the model doesnt quite reproduce correctly, or indeed you just want to improve the reproduced voice, then finetuning is a way to train your "XTTSv2 local" model **(stored in `/alltalk_tts/models/xxxxx/`)** on a specific voice. For this you will need: + + - An Nvidia graphics card. (Please see the help section [note](https://github.com/erew123/alltalk_tts/edit/main/README.md#performance-and-compatibility-issues) if you have multiple Nvidia GPU's). + - 18GB of disk space free (most of this is used temporarily) + - At least 2 minutes of good quality speech from your chosen speaker in mp3, wav or flacc format, in one or more files (have tested as far as 20 minutes worth of audio). + - As a side note, many people seem to think that the Whisper v2 model (used on Step 1) is giving better results at generating training datasets, so you may prefer to try that, as opposed to the Whisper 3 model. + + #### ⚫ How will this work/How complicated is it? + Everything has been done to make this as simple as possible. At its simplest, you can literally just download a large chunk of audio from an interview, and tell the finetuning to strip through it, find spoken parts and build your dataset. You can literally click 4 buttons, then copy a few files and you are done. At it's more complicated end you will clean up the audio a little beforehand, but its still only 4x buttons and copying a few files. + + #### ⚫ The audio you will use + I would suggest that if its in an interview format, you cut out the interviewer speaking in audacity or your chosen audio editing package. You dont have to worry about being perfect with your cuts, the finetuning Step 1 will go and find spoken audio and cut it out for you. Is there is music over the spoken parts, for best quality you would cut out those parts, though its not 100% necessary. As always, try to avoid bad quality audio with noises in it (humming sounds, hiss etc). You can try something like [Audioenhancer](https://audioenhancer.ai/) to try clean up noisier audio. There is no need to down-sample any of the audio, all of that is handled for you. Just give the finetuning some good quality audio to work with. + + #### ⚫ Can I Finetune a model more than once on more than one voice + Yes you can. You would do these as multiple finetuning's, but its absolutely possible and fine to do. Finetuning the XTTS model does not restrict it to only being able to reproduce that 1x voice you trained it on. Finetuning is generally nudging the model in a direction to learn the ability to sound a bit more like a voice its not heard before. + + #### ⚫ A note about anonymous training Telemetry information & disabling it + Portions of Coqui's TTS trainer scripts gather anonymous training information which you can disable. Their statement on this is listed [here](https://github.com/coqui-ai/Trainer?tab=readme-ov-file#anonymized-telemetry). If you start AllTalk Finetuning with `start_finetuning.bat` or `./start_finetuning.sh` telemetry will be disabled. If you manually want to disable it, please expand the below: + +
+ Manually disable telemetry
+ + Before starting finetuning, run the following in your terminal/command prompt: + + - On Windows by typing `set TRAINER_TELEMETRY=0` + - On Linux & Mac by typing `export TRAINER_TELEMETRY=0` + + Before you start `finetune.py`. You will now be able to finetune offline and no anonymous training data will be sent. +
+ """ + + finetuning_setup = """ + #### ⚫ Prerequisites for Fine-tuning with Nvidia CUDA Development Toolkit 11.8 + + All the requirements for Finetuning will be installed by using the atsetup utility and installing your correct requirements (Standalone or for Text-generation-webui). The legacy manual instructions are stored below, however these shouldnt be required. + +
+ Legacy manual instructions for installing Nvidia CUDA Development Toolkit 11.8
+ - To perform fine-tuning, a specific portion of the **Nvidia CUDA Development Toolkit v11.8** must be installed. This is crucial for step 1 of fine-tuning. The objective is to minimize the installation footprint by installing only the essential components. + - The **Nvidia CUDA Development Toolkit v11.8** operates independently from your graphics card drivers and the CUDA version utilized by your Python environment. + - This installation process aims to keep the download and install size as minimal as possible, however a full install of the tookit requires 3GB's of disk space. + - When running Finetuning it will require upto 20GB's of temporary disk space, so please ensure you have this space available and preferably use a SSD or NVME drive. + + 1. **Download the Toolkit**: + - Obtain the **network install** version of the Nvidia CUDA Development Toolkit 11.8 from [Nvidia's Archive](https://developer.nvidia.com/cuda-11-8-0-download-archive). + + 2. **Run the Installer**: + - Choose **Custom (Advanced)** installation. + - Deselect all options initially. + - Select the following components: + - `CUDA` > `Development` > `Compiler` > `nvcc` + - `CUDA` > `Development` > `Libraries` > `CUBLAS` (**both** development and runtime) + + 3. **Configure Environment Search Path**: + - It's essential that `nvcc` and CUDA 11.8 library files are discoverable in your environment's search path. Adjustments can be reverted post-fine-tuning if desired. + + **For Windows**: + - Edit the `Path` environment variable to include `C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8\bin`. + - Add `CUDA_HOME` and set its path to `C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8.` + + **For Linux**: + - The path may vary by Linux distribution. Here's a generic setup: + - `export CUDA_HOME=/usr/local/cuda` + - `export PATH=${CUDA_HOME}/bin:${PATH}` + - `export LD_LIBRARY_PATH=${CUDA_HOME}/lib64:$LD_LIBRARY_PATH` + + - Consider adding these to your `~/.bashrc` for permanence, or apply temporarily for the current session by running the above commands each time you start your Python environment. + + **Note**: If using Text-generation-webui, its best to set these temporarily. + + 4. **Verify Installation**: + - Open a **new** terminal/command prompt to refresh the search paths. + - In a terminal or command prompt, execute `nvcc --version`. + - Success is indicated by a response of `Cuda compilation tools, release 11.8.` Specifically, ensure it is version 11.8. + + 5. **Troubleshooting**: + - If the correct version isn't reported, recheck your environment path settings for accuracy and potential conflicts with other CUDA versions. +
+ + #### Additional Note on Torch and Torchaudio: + - Ensure Torch and Torchaudio are CUDA-enabled (any version), which is separate from the CUDA Toolkit installation. CUDA 11.8 corresponds to `cu118` and CUDA 12.1 to `cu121` in AllTalk diagnostics. + - Failure to install CUDA for Torch and Torchaudio will result in Step 2 of fine-tuning failing. These requirements are distinct from the CUDA Toolkit installation, so avoid conflating the two.
+ + + #### ⚫ Starting Fine-tuning + + **NOTE:** Ensure AllTalk has been launched at least once after any updates to download necessary files for fine-tuning. + + 1. **Close Resource-Intensive Applications**: + - Terminate any applications that are using your GPU/VRAM to ensure enough resources for fine-tuning. + + 2. **Organize Voice Samples**: + - Place your audio samples into the following directory: + `/alltalk_tts/finetune/put-voice-samples-in-here/` + + Depending on your setup (Text-generation-webui or Standalone AllTalk), the steps to start the Python environment vary: + + ### **For Standalone AllTalk Users**: + Navigate to the `alltalk_tts` folder: + > `cd alltalk_tts` + + Start the Python environment: + > **Windows**: `start_finetune.bat`
+ **Linux**: `./start_finetune.sh` + + ### **For Text-generation-webui Users**: + Navigate to the Text-generation-webui directory: + >`cd text-generation-webui` + + Start the Python environment suitable for your OS:
+ **Windows**: `cmd_windows.bat`
+ **Linux**: `./cmd_linux.sh`
+ **macOS**: `cmd_macos.sh`
+ **WSL (Windows Subsystem for Linux)**: `cmd_wsl.bat` + + Move into the AllTalk directory: + >`cd extensions/alltalk_tts` + + **Linux** users only need to run this command: + ` + export LD_LIBRARY_PATH=`python3 -c 'import os; import nvidia.cublas.lib; import nvidia.cudnn.lib; print(os.path.dirname(nvidia.cublas.lib.__file__) + ":" + os.path.dirname(nvidia.cudnn.lib.__file__))'` + ` + Start the fine-tuning process with the command: + > `python finetune.py`

+ + > If you're unfamiliar with Python environments and wish to learn more, consider reviewing **Understanding Python Environments Simplified** in the Help section. + + ### Both Standalone & Text-generation-webui users + 3. **Pre-Flight Checklist**: + > Go through the pre-flight checklist to ensure readiness. Address any issues flagged as "Fail". + + 4. **Post Fine-tuning Actions**: + > Upon completing fine-tuning, the final tab will guide you on managing your files and relocating your newly trained model to the appropriate directory. + """ + + finetuning_details = """ + #### ⚫ How many Epochs etc is the right amount? + + In finetuning the suggested/recommended amount of epochs, batch size, evaluation percent etc is already set. However, there is no absolutely correct answer to what the settings should be, it all depends on what you are doing. + + - If you just want to train a normal human voice that is in an existing language, for most people’s needs, the base settings would work fine. You may choose to increase the epochs up to maybe 20, or run a second round of training if needed. + - If you were training an entirely new language, you would need a huge amount of training data and it requires around 1000 epochs (based on things I can find around the internet of people who tried this). + - If you are training a cartoon style voice in an existing language, it may need well upwards of 40 epochs until it can reproduce that voice with some success. + + There are no absolute correct settings, as there are too many variables, ranging from the amount of samples you are using (5 minutes worth? 4 hours worth? etc), if they are similar samples to what the AI model already understands, so on and so forth. Coqui whom originally trained the model usually say something along the lines of, once you’ve trained it X amount, if it sounds good then you are done and if it doesn’t, train it more. + + #### ⚫ Evaluation Data Percentage + In the process of finetuning, it's crucial to balance the data used for training the model against the data reserved for evaluating its performance. Typically, a portion of the dataset is set aside as an 'evaluation set' to assess the model's capabilities in dealing with unseen data. On Step 1 of finetuning you have the option to adjust this evaluation data percentage, offering more control over your model training process.

+ **Why Adjust the Evaluation Percentage?**

+ Adjusting the evaluation percentage **can** be beneficial in scenarios with limited voice samples. When dealing with a smaller dataset, allocating a slightly larger portion to training could enhance the model's ability to learn from these scarce samples. Conversely, with abundant data, a higher evaluation percentage might be more appropriate to rigorously test the model's performance. There are currently no absolutely optimal split percentages as it varies by dataset. + - **Default Setting:** The default evaluation percentage is set at 15%, which is a balanced choice for most datasets. + - **Adjustable Range:** Users can now adjust this percentage, but it’s generally recommend keeping it between 5% and 30%. + - **Lower Bound:** A minimum of 5% ensures that there's enough data to evaluate model performance. + - **Upper Bound:** Its suggested not exceeding 30% for evaluation to avoid limiting the amount of data available for training. + + - **Understanding the Impact:** Before adjusting this setting, it's important to understand its impact on model training and evaluation. Incorrect adjustments can lead to suboptimal model performance. + - **Gradual Adjustments:** For those unfamiliar with the process, we recommend reading up on training data and training sets, then making small, incremental changes and observing their effects. + - **Data Quality:** Regardless of the split, the quality of the audio data is paramount. Ensure that your datasets are built from good quality audio with enough data within them. + + #### ⚫ Using a Finetuned model in Text-generation-webui + + At the end of the finetune process, you will have an option to `Compact and move model to /trainedmodel/` this will compact the raw training file and move it to `/model/trainedmodel/`. When AllTalk starts up within Text-generation-webui, if it finds a model in this location a new loader will appear in the interface for `XTTSv2 FT` and you can use this to load your finetuned model.

**Be careful** not to train a new model from the base model, then overwrite your current `/model/trainedmodel/` **if** you want a seperately trained model. This is why there is an `OPTION B` to move your just trained model to `/models/lastfinetuned/`. + + #### ⚫ Training one model with multiple voices + + At the end of the finetune process, you will have an option to `Compact and move model to /trainedmodel/` this will compact the raw training file and move it to `/model/trainedmodel/`. This model will become available when you start up finetuning. You will have a choice to train the Base Model or the `Existing finetuned model` (which is the one in `/model/trainedmodel/`). So you can use this to keep further training this model with additional voices, then copying it back to `/model/trainedmodel/` at the end of training. + + #### ⚫ Do I need to keep the raw training data/model? + + If you've compacted and moved your model, its highly unlikely you would want to keep that data, however the choice is there to keep it if you wish. It will be between 5-10GB in size, so most people will want to delete it. + + #### ⚫ I have deeper questions about training the XTTS model, where can I find more information? + + If you have deeper questions about the XTTS model, its capabilites, the training process etc, anything thats not covered within the above text or the interface of `finetune.py`, please use the following links to research Coqui's documentation on the XTTS model. + + - https://docs.coqui.ai/en/latest/models/xtts.html + - https://github.com/coqui-ai/TTS + - https://github.com/coqui-ai/TTS/discussions + + """ + + tts_generator = """ + ### ⬜ AllTalk TTS Generator + AllTalk TTS Generator is the solution for converting large volumes of text into speech using the voice of your choice. Whether you're creating audio content or just want to hear text read aloud, the TTS Generator is equipped to handle it all efficiently. Please see here for a quick [demo](https://www.youtube.com/watch?v=hunvXn0mLzc)

The link to open the TTS generator can be found on the built-in Settings and Documentation page.

**DeepSpeed** is **highly** recommended to speed up generation. **Low VRAM** would be best turned off and your LLM model unloaded from your GPU VRAM (unload your model). **No Playback** will reduce memory overhead on very large generations (15,000 words or more). Splitting **Export to Wav** into smaller groups will also reduce memory overhead at the point of exporting your wav files (so good for low memory systems). + + #### ⬜ Estimated Throughput + This will vary by system for a multitude of reasons, however, while generating a 58,000 word document to TTS, with DeepSpeed enabled, LowVram disabled, splitting size 2 and on an Nvidia RTX 4070, throughput was around 1,000 words per minute. Meaning, this took 1 hour to generate the TTS. Exporting to combined wavs took about 2-3 minutes total. + + #### ⬜ Quick Start + > **Text Input:** Enter the text you wish to convert into speech in the 'Text Input' box.
+ > **Generate TTS:** Hit this to start the text-to-speech conversion.
+ > **Pause/Resume:** Used to pause and resume the playback of the initial generation of wavs or the stream.
+ > **Stop Playback:** This will stop the current audio playing back. It does not stop the text from being generated however. + + Once you have sent text off to be generated, either as a stream or wav file generation, the TTS server will remain busy until this process has competed. As such, think carefully as to how much you want to send to the server.
+ If you are generating wav files and populating the queue, you can generate one lot of text to speech, then input your next lot of text and it will continue adding to the list. + + #### ⬜ Customization and Preferences + > **Character Voice:** Choose the voice that will read your text.
+ > **Language:** Select the language of your text.
+ > **Chunk Sizes:** Decide the size of text chunks for generation. Smaller sizes are recommended for better TTS quality. + + #### ⬜ Interface and Accessibility + > **Dark/Light Mode:** Switch between themes for your visual comfort.
+ > **Word Count and Generation Queue:** Keep track of the word count and the generation progress. + + #### ⬜ TTS Generation Modes + > **Wav Chunks:** Perfect for creating audio books, or anything you want to keep long term. Breaks down your text into manageable wav files and queues them up. Generation begins automatically, and playback will start after a few chunks have been prepared ahead. You can set the volume to 0 if you don’t want to hear playback. With Wav chunks, you can edit and/or regenerate portions of the TTS as needed.
+ + > **Streaming:** For immediate playback without the ability to save. Ideal for on-the-fly speech generation and listening. This will not generate wav files and it will play back through your browser. You cannot stop the server generating the TTS once it has been sent.
+ + With wav chunks you can either playback “In Browser” which is the web page you are on, or “On Server” which is through the console/terminal where AllTalk is running from, or "No Playback". Only generation “In Browser” can play back smoothly and populate the Generated TTS List. Setting the Volume will affect the volume level played back both “In Browser” and “On Server”.
+ + For generating **large amounts of TTS**, it's recommended to select the **No Playback** option. This setting minimizes the memory usage in your web browser by avoiding the loading and playing of audio files directly within the browser, which is particularly beneficial for handling extensive audio generations. The definition of large will vary depending on your system RAM availability (will update when I have more information as to guidelines). Once the audio is generated, you can export your list to JSON (for safety) and use the **Play List** option to play back your audio. + + #### ⬜ Playback and List Management + > **Playback Controls:** Utilize 'Play List' to start from the beginning or 'Stop Playback' to halt at any time.
+ > **Custom Start:** Jump into your list at a specific ID to hear a particular section.
+ > **Regeneration and Editing:** If a chunk isn't quite right, you can opt to regenerate it or edit the text directly. Click off the text to save changes and hit regenerate for the specific line.
+ > **Export/Import List:** Save your TTS list as a JSON file or import one. Note: Existing wav files are needed for playback. Exporting is handy if you want to take your files away into another program and have a list of which wav is which, or if you keep your audio files, but want to come back at a later date, edit one or two lines, regenerate the speech and re-combine the wav’s into one new long wav.
+ + #### ⬜ Exporting Your Audio + > **Export to WAV:** Combine all generated TTS from the list, into one single WAV file for easy download and distribution. Its always recommended to export your list to a JSON before exporting, so that you have a backup, should something go wrong. You can simply re-import the list and try exporting again.
+ + When exporting, there is a file size limit of 1GB and as such you have the option to choose how many files to include in each block of audio exported. 600 is just on the limit of 1GB, depending on the average file size, so 500 or less is a good amount to work with. You can combine the generated files after if you wish, in Audacity or similar.
+ + Additionally, lower export batches will lower the memory requirements, so if your system is low on memory (maybe 8 or 16GB system), you can use smaller export batches to keep the memory requirement down. + + #### ⬜ Exporting Subtitles (SRT file) + > **Export SRT:** This will scan through all wav files in your list and generate a subtitles file that will match your exported wav file. + + #### ⬜ Analyzing generated TTS for errors + > **Analyze TTS:** This will scan through all wav files comparing each ID's orignal text with the TTS generated for that ID and then flag up inconsistences. Its important to understand this is a **best effort** process and **not 100% perfect**, for example:

+ + > Your text may have the word `their` and the automated routine that listens to your generated TTS interprets the word as `there`, aka a spelling difference.
+ > Your text may have `Examples are:` (note the colon) and the automated routine that listens to your generated TTS interprets the word as `Examples are` (note NO colon as you cannot sound out a colon in TTS), aka a punctuation difference.
+ > Your text may have `There are 100 items` and the automated routine that listens to your generated TTS interprets the word as `There are one hundred items`, aka numbers vs the number written out in words.
+ + There will be other examples such as double quotes. As I say, please remember this is a **best effort** to help you identify issues.
+ + As such, there is a `% Accuracy` setting. This uses a couple of methods to try find things that are similar e.g. taking the `their` and `there` example from above, it would identify that they both sound the same, so even if the text says `their` and the AI listening to the generated TTS interprets the word as `there`, it will realise that both sound the same/are similar so there is no need to flag that as an error. However, there are limits to this and some things may slip through or get picked up when you would prefer them not to be flagged. + + The higher the accuracy you choose, the more things it will flag up, however you may get more unwanted detections. The lower the less detections. Based on my few tests, accuracy settings between 96 to 98 seem to generally give the best results. However, I would highly recommend you test out a small 10-20 line text and test out the **Analyze TTS** button to get a feel for how it responds to different settings, as well as things it flags up. + + You will be able to see the ID's and Text (orignal and as interpreted) by looking at the terminal/command prompt window. + + The Analyze TTS feature uses the Whisper Larger-v2 AI engine, which will download on first use if necessary. This will require about 2.5GB's of disk space and could take a few minutes to download, depending on your internet connection. + + You can use this feature on systems that do not have an Nvidia GPU, however, unless you have a very powerful CPU, expect it to be slow. + + #### ⬜ Tricks to get the model to say things correctly + Sometimes the AI model won’t say something the way that you want it to. It could be because it’s a new word, an acronym or just something it’s not good at for whatever reason. There are some tricks you can use to improve the chances of it saying something correctly. + + **Adding pauses**
+ You can use semi-colons ";" and colons ":" to create a pause, similar to a period "." which can be helpful with some splitting issues. + + **Acronyms**
+ Not all acronyms are going to be pronounced correctly. Let’s work with the word `ChatGPT`. We know it is pronounced `"Chat G P T"` but when presented to the model, it doesn’t know how to break it down correctly. So, there are a few ways we could get it to break out "Chat" and the G P and T. e.g.
+ + > `Chat G P T.`
+ > `Chat G,P,T.`
+ > `Chat G.P.T.`
+ > `Chat G-P-T.`
+ > `Chat gee pee tea`
+ + All bar the last one are using ways within the English language to split out "Chat" into one word being pronounced and then split the G, P and T into individual letters. The final example, which is to use Phonetics will sound perfectly fine, but clearly would look wrong as far as human readable text goes. The phonetics method is very useful in edge cases where pronunciation difficult. + + #### ⬜ Notes on Usage + > For seamless TTS generation, it's advised to keep text chunks under 250 characters, which you can control with the Chunk sizes.
+ > Generated audio can be played back from the list, which also highlights the currently playing chunk.
+ > The TTS Generator remembers your settings, so you can pick up where you left off even after refreshing the page.
+ """ + + with gr.Tab("Documentation and Help"): + with gr.Tab("Narrator Function"): gr.Markdown(narrator_guides) + with gr.Tab("RVC"): + gr.Markdown(rvc_guides) + with gr.Row(): + with gr.Column(): gr.Markdown(rvc_guides2) + with gr.Column(): gr.Markdown(rvc_guides3) + with gr.Tab("Low VRAM"): gr.Markdown(low_vram) + with gr.Tab("SillyTavern"): gr.Markdown(silly_tavern_support) + with gr.Tab("Updating"): gr.Markdown(updating_alltalk) + with gr.Tab("DeepSpeed"): gr.Markdown(deepspeed_guides) + with gr.Tab("Help with Problems"): gr.Markdown(help_with_problems) + with gr.Tab("XTTS Finetuning"): + with gr.Tab("XTTS Finetuning basics"): gr.Markdown(finetuning_a_model) + with gr.Tab("XTTS Finetuning Setup"): gr.Markdown(finetuning_setup) + with gr.Tab("XTTS Finetuning Detailed"): gr.Markdown(finetuning_details) + with gr.Tab("TTS Generator"): gr.Markdown(tts_generator) \ No newline at end of file diff --git a/system/gradio_pages/alltalk_generation_help.py b/system/gradio_pages/alltalk_generation_help.py new file mode 100644 index 0000000..68612e9 --- /dev/null +++ b/system/gradio_pages/alltalk_generation_help.py @@ -0,0 +1,99 @@ +import gradio as gr + +def alltalk_generation_help(): + generation_blurb = """ + The Generation Screen is the main interface where you can input text to generate speech, as well as swap **and set the default** Text-to-Speech (TTS) engine and model. Below is a detailed description of each setting and its functionality. + """ + generation_help = """ + ### 🟨 TTS Engine and Model
+ **Swap TTS Engine**
+ > This button allows you to change and set the current TTS engine. Swapping the engine will also set the new engine as the default for future sessions. When you swap the engine, the settings on the page will refresh to match the capabilities of the selected engine. Some settings may become unavailable or newly available depending on the chosen engine.
+ + **Load Different Model**
+ > This dropdown lists the available models for the selected TTS engine. These models are located in the models folder and are specific to each TTS engine. Changing the model will also set it as the default for future sessions and refresh the settings on the page.
+ + ### 🟨 RVC Pipeline
+ **RVC Character Voice**
+ > This setting is part of the RVC pipeline. If RVC is not enabled (which can be done in the RVC tab), "Disabled" will be the only option. Once RVC is enabled and the required models (approximately 700MB) are downloaded, any voices stored in the `/models/rvc_voices/{yourmodelhere}` folder will be available for selection. Selecting a voice will apply it during the TTS generation process. You can select "Disabled" at any time to bypass the RVC pipeline even when it is globally enabled.
+ + **RVC Narrator Voice**
+ > Similar to the RVC Character Voice, this setting applies to the narrator voice in the RVC pipeline.
+ + ### 🟨 Generation Settings
+ **Generation Mode**
+ > Choose the mode of TTS generation, such as Standard or Streaming. Note that Gradio may have difficulty with streaming, so you might receive standard audio as a file even when streaming is selected. If the engine doesn't support Streaming, only Standard mode will be shown.
+ + **Language/Model not multi-language**
+ > Select the language for TTS generation here. Some models are **not** multi-language capable and the language it generates in is built into the model itself. In such cases, this option will change to `Model not multi-language` and it will no longer be selectable when the model doesn't support multiple languages.
+ + **Narrator Enabled/Disabled**
+ > Enable or disable the narrator feature. When enabled, the TTS engine processes three types of text (Refer to the "Narrator Function" help tab for more details):
+ > **Narrated Text** : Text enclosed in asterisks *Narrated text*.
+ > **Character Text** : Text enclosed in double quotes "Character text".
+ > **Text-Not-Inside**: Any text not enclosed in asterisks or double quotes. The handling of this text is customizable.
+ + **Narrator Text-not-inside**
+ > Configures how text that is not enclosed in asterisks or double quotes is handled. Detailed explanations are available in the "Narrator Function" help tab.
+ + **Auto-Stop Current Generation**
+ > If the loaded model or generation method supports stopping the current TTS generation when sending a new request, this setting will attempt to stop the current process before starting a new one. + + ### 🟨 Voice Selection
+ **Character (Main TTS) Voice**
+ > Select the character voice from the available voices within the currently loaded TTS engine and model.
+ + **Narrator Voice**
+ > Choose the narrator voice from the available voices within the currently loaded TTS engine and model.
+ + """ + generation_help2 = """ + ### 🟨 Additional Settings + **Text Filtering**
+ > **None**: No filtering. Raw text is sent to the TTS engine, which may result in odd sounds with some special characters.
+ > **HTML**: For HTML content, using HTML entities like emojis or text characters.
+ > **Standard**: Human-readable text with basic filtering to clean up special characters.
+ + **Play Locally or Remotely**
+ > Choose to play the generated speech locally or on the terminal/console where the server is running.
+ + **Remote Play Volume**
+ > Adjust the volume for remote playback.
+ + **Output File Name**
+ > Specify the name for the output file.
+ + **Include Timestamp**
+ > If enabled, each generated file will include a timestamp to ensure uniqueness. If disabled, the output filename will be used, and the file will be overwritten with each generation.
+ + **Speed**
+ > Adjust the speed of the generated speech. This option is not available if the selected TTS engine doesn't support it.
+ + **Pitch**
+ >Adjust the pitch of the generated speech. This option is not available if the selected TTS engine doesn't support it.
+ + **Temperature**
+ > Control the randomness of the generation process. Higher values result in more varied output. This option is not available if the selected TTS engine doesn't support it.
+ + **Repetition Penalty**
+ > Set the penalty for repetitive sequences in the generated speech. This option is not available if the selected TTS engine doesn't support it.
+ + ### 🟨 Output and Controls + **TTS Result**
+ > View the result of the TTS generation process here.
+ + **Playback Controls**
+ > Use these controls to play, pause, and navigate through the generated speech.
+ + **Light/Dark Mode**
+ > Toggle between light and dark mode for the interface.
+ + **Refresh Server Settings**
+ >Refresh the server settings to apply any changes made. This is useful if you have reloaded the engine elsewhere or added new models/voices to your folders. If refreshing doesn't update your voices, you can use the "Swap TTS Engine" button to force a full refresh.
+ + **Interrupt TTS Generation**
+ > Manually stop the current TTS generation process, similar to the "Auto-Stop Current Generation" setting but applied manually.
+ """ + gr.Markdown(generation_blurb) + with gr.Row(): + gr.Markdown(generation_help) + gr.Markdown(generation_help2) \ No newline at end of file diff --git a/system/gradio_pages/alltalk_welcome.py b/system/gradio_pages/alltalk_welcome.py new file mode 100644 index 0000000..24477ff --- /dev/null +++ b/system/gradio_pages/alltalk_welcome.py @@ -0,0 +1,59 @@ +import os +import json +import pathlib as Path +import gradio as gr + +def modify_config(): + config_file = os.path.join("confignew.json") + with open(config_file, "r") as f: + config = json.load(f) + config["firstrun_splash"] = False + with open(config_file, "w") as f: + json.dump(config, f, indent=4) + return f"Welcome screen is disabled." + +def alltalk_welcome(): + welcome = """ + ## 🟧 Welcome the the AllTalk v2 BETA + Thanks for trying out the BETA. Ive no doubt there will be bugs and quirks here and there. Most errors/issues should have outputs at the command prompt/console. + + I have validated this BETA to run on Windows/Linux, because I dont have a Mac. With Python 3.11.9, Pytorch 2.2.1 with CUDA 12.1 extensions installed. Thats not to say it wont work on Mac or other versions of Python and Pytorch, its just there would be some extra testing/figuring things out to make it work. Obviously, I am kind of a bit limited to do that on Mac's. Certainly Mac's dont need the Nvidia lines in the requirements file and I cannot say how certain engines will/wont work/perform on Mac's. + + ### 🟧 Important Requirements + > - Please ensure that you have installed Espeak-ng for your platform (you should get and error message at the command prompt/terminal if you havnt).
+ > - It is possible I have forgotten some requirements, but hopefully not! + > - Linux users, you should have installed `libaio-dev` or `libaio-devl` (depending on your Linux flavour), otherwise DeepSpeed will fail e.g.
+ >         **Debian-based systems** `sudo apt install libaio-dev`
+ >         **RPM-based systems** `sudo yum install libaio-devel` + + ### 🟧 TLDR what you need to do + > - To download models, you can go to the `TTS Engines Settings`, choose the engine you want and there are downloads there, along with additional instructions/information about each engine, its specific settings, how to work with it etc. + > - RVC when enabled will download about 800MB of models that it needs to work. If you want more information on RVC, please read the Documentation. + > - Talking of which, Gradio isnt great at documentation. Ive found that the `Light/Dark Mode` button is handy, along with sometime using your browsers zoom function. + > - **Check the console** for errors/warnings if you have problems. I have **tried** to cover most issues/possible errors. + > - The documentation may be a bit rough and ready. I havnt had time to go through it all step by step. + + ### 🟧 DeepSpeed on Linux + > I've not managed to update the documentation, but if you are **NOT** using AllTalk as a standalone on Linux, take a look here and this should ease the setup of DeepSpeed https://github.com/erew123/alltalk_tts/releases/tag/DeepSpeed-14.2-Linux + + ### 🟧 SillyTavern users + > There is an updated ST extension in the system folder. Instructions are inside the folder. + + ### 🟧 Text-generation-webui users + > There is a TGWUI remote extension, if you want to run AllTalk on a computer that is NOT your AllTalk system. Please look in the system folder. Instructions are inside the folder. + + ### 🟧 Why is X feature no in the BETA yet? Or I have a great idea! + Mostly because of time restrains. V2 has been a massive re-write of code in the 100's of hours range to get it where it is. Please do check the Feature Requests list here https://github.com/erew123/alltalk_tts/discussions/74. I will get to them when I can. People are welcome to work on code/test things out with this version if they wish. I will set up a page for feeback on the **Github Discussions Board** https://github.com/erew123/alltalk_tts/discussions + + ### 🟧 Can I add other TTS engines? + Yes if you have a bit of coding experience. All the Coqui supported engines should be pretty easy to set up now, I just havnt gotten around it yet. Other engines like Piper and Parler have been done of course, showing other engines can be imported. Rough instructions are in the `system/tts_engines/template` folder. + + ### 🟧 Showing Your Support + > If AllTalk has been helpful to you, consider showing your support through a donation on my **[Ko-fi page](https://ko-fi.com/erew123)**. Your support is greatly appreciated and helps ensure the continued development and improvement of AllTalk. + """ + with gr.Tab("AllTalk v2 BETA"): + gr.Markdown(welcome) + with gr.Row(): + output = gr.Textbox(label="Output") + update_btn = gr.Button("Dont show me this screen again!") + update_btn.click(fn=modify_config, inputs=None, outputs=output) \ No newline at end of file diff --git a/system/gradio_pages/api_documentation.py b/system/gradio_pages/api_documentation.py new file mode 100644 index 0000000..ec42b32 --- /dev/null +++ b/system/gradio_pages/api_documentation.py @@ -0,0 +1,580 @@ +import gradio as gr + +def api_documentation(): + best_practices = """ + ### 🟠 Best Practices for writing an extension + + You will need to store the IP address and Port (minimum) that your extension will be connecting to. Potentially also http/https if you wish to use AllTalk over tunnels. Additionally, you may also wish to store a connection timeout value.
+ + ``` + { + "api_alltalk_protocol": "http://", + "api_alltalk_ip_port": "127.0.0.1:7851", + "api_connection_timeout": 5 + } + ``` + When your extension starts up you will need to connect to the IP/Port and check the **/api/ready** end point for a 'Ready' status. You may wish to do this on a loop for the period of the connection timeout.
+ + If the timeout is reached without a response, you can set voices, models available, models loaded, basically whatever values you are going to store/present to the user as "Server Offline" or whatever terminology you wish.
+ + If the connection is available, then you would:
+ + > 1) Pull the current settings and populate those into a paramaters variabele. **/api/currentsettings**
+ 2) Pull the current voices and populate those into a paramaters variabele. **/api/voices**
+ 3) Pull the current rvcvoices and populate those into a paramaters variabele. **/api/rvcvoices**
+ + You can now present those to the user interface, choosing if you are just going to present the voices (narrator or not), RVC voices, model loaded, models available etc.
+ + If you are going to present a multitude of options, please abide by the **xxxxxx_capable** settings provided by **/api/currentsettings**, disabling or enabling options in your user interface as the loaded in TTS engine does or doesnt allow for e.g. if the current TTS engine doesnt support changing your generation audio speed, it will send **"generationspeed_capable": false** over when you use **/api/currentsettings**. As such, you should disable the option to change the generation speed within your interface, however, ultimately, even if not disabled, if the current TTS engine loaded isnt capable of speed generation, changing the generation speed will have no effect on the resulting TTS generation, but you may end up with users asking why!
+ + It is also advisable to have a refresh button to pull make AllTalk update its settings **/api/reload_config** and then you can pull current settings & voices again. This ensures the use always has a way to reconnect to the AllTalk server if its unavailable when your extension starts, or in the scenario that something else was changed in the Gradio interface and now the TTS Engine loaded in is different from when your extension started up.
+ + You can obviously choose if you will give the user access to change the model loaded, TTS engine etc. Please read the documentation on the API endpoints for more information.
+ + If you are presenting all the variables/options to the user, then when you send off a generation request to **/api/tts-generate** send all of the variable/options that are stored within your interface.
+ + If you are NOT presenting all the variables/options to the user, then you have 2x options when sending to **/api/tts-generate**:
+ + > 1) Send all the values/options but hard code some settings in your outbound generation request. This will ensure you always get the TTS generation result you want. Please read the **/api/tts-generate** section for details.
+ + OR
+ + >2) Dont send all the values/options in the generation request. In this scenario, the AllTalk server will populate missing settings from the global API Default Settings and the current TTS Engine's settings that are loaded in. e.g. if you dont provide a character voice, the default set character voice of whatever TTS engine is loaded in will be used. The same goes for all the other settings, except the actual text of the generation request.
+ + Finally, depending on how you are storing your settings, for your extension, you may wish to save the settings each time there is a successful generation request. This ensures that the settings are valid and the connected IP address/port are correct. + """ + + settings_endpoints = """ + ### 🟠 Ready Endpoint
+ Check if the Text-to-Speech (TTS) engine has started and is ready to accept requests. This endpoint is useful when performing reload model/engine requests as it will return "Unloaded" while the engine is restarting. You can poll this endpoint every second or so to confirm a "Ready" status, indicating AllTalk is ready to process requests. Note that if there is an issue loading the selected model (e.g., missing from the file system), the endpoint will still return "Ready". This status only indicates that the engine has loaded.
+ +          **URL**: `http://{ipaddress}:{port}/api/ready`
+          **Method**: `GET`
+ + > `curl -X GET "http://127.0.0.1:7851/api/ready"` + + Response: `Ready` or `Unloaded` + + ### 🟠 Standard Voices List Endpoint
+ Retrieve a list of available voices for generating speech that the currently loaded TTS engine and model supports. If the currently loaded TTS engine does not load a model directly (e.g., Piper), the models themselves will be displayed as voices. + +          **URL**: `http://{ipaddress}:{port}/api/voices`
+          **Method**: `GET`
+ + > `curl -X GET "http://127.0.0.1:7851/api/voices"` + + ``` + { + "status": "success", + "voices": ["voice1", "voice2", "voice3"] + } + + ``` + + ### 🟠 RVC Voices List Endpoint
+ Retrieve a list of available RVC voices for further processing your TTS with the RVC pipeline. `Disabled` will always be included in the list. When `Disabled` is used, the RVC pipeline will not be utilized. If the RVC pipeline is globally disabled in AllTalk, `Disabled` will be the only item in the list. Index files matching their RVC voices will not be displayed; if an index file exists for an RVC voice, it will be automatically selected during generation. If multiple index files exist and it is impossible to automatically select one, a message will be output in the console. + +          **URL**: `http://{ipaddress}:{port}/api/rvcvoices`
+          **Method**: `GET`
+ + > `curl -X GET "http://127.0.0.1:7851/api/rvcvoices"` + + ``` + { + "status": "success", + "voices": ["Disabled", "folder1\\voice1.pth", "folder2\\voice2.pth", "folder3\\voice3.pth"] + } + ``` + + ### 🟠 Current Settings Endpoint
+ Retrieve the current settings of the TTS engine. + +          **URL**: `http://{ipaddress}:{port}/api/currentsettings`
+          **Method**: `GET`
+ + > `curl -X GET "http://127.0.0.1:7851/api/currentsettings"` + + + ``` + { + "engines_available": ["parler", "piper", "vits", "xtts"], + "current_engine_loaded": "xtts", + "models_available": [ + {"name": "xtts - xttsv2_2.0.2"}, + {"name": "apitts - xttsv2_2.0.2"}, + {"name": "xtts - xttsv2_2.0.3"}, + {"name": "apitts - xttsv2_2.0.3"} + ], + "current_model_loaded": "xtts - xttsv2_2.0.3", + "manufacturer_name": "Coqui", + "audio_format": "wav", + "deepspeed_capable": true, + "deepspeed_available": true, + "deepspeed_enabled": true, + "generationspeed_capable": true, + "generationspeed_set": 1, + "lowvram_capable": true, + "lowvram_enabled": false, + "pitch_capable": false, + "pitch_set": 0, + "repetitionpenalty_capable": true, + "repetitionpenalty_set": 10, + "streaming_capable": true, + "temperature_capable": true, + "temperature_set": 0.75, + "ttsengines_installed": true, + "languages_capable": true, + "multivoice_capable": true, + "multimodel_capable": true + } + ``` + + #### **engines_available** + > Lists the currently available TTS engines that can be loaded. + + #### **current_engine_loaded** + > Shows the currently loaded TTS engine. + These can be used together with the **/api/enginereload** endpoint to determine and change the currently loaded engine. Note that swapping the engine using this method can cause the main script process of AllTalk to lose sync with the subprocess. This results in a message indicating that script.py lost control of the subprocess when exiting AllTalk. + + #### **models_available** + > Lists all the models available for the currently loaded TTS engine. Displayed as {engine-name - model-name} from models_available. + + #### **current_model_loaded** + > Shows the currently loaded model. + You can swap models using the **/api/reload** endpoint by specifying the full {enginename - modelname}. + + #### **audio_format** + > The primary format in which the currently loaded engine produces audio output. This does not account for any global transcoding settings in the AllTalk Settings page but is used by the transcoding process to determine if transcoding is necessary. + + #### **deepspeed_capable** + > Indicates if the current TTS engine supports DeepSpeed and can handle DeepSpeed enable/disable requests. + + #### **deepspeed_available** + > If the current TTS engine is DeepSpeed capable, shows whether DeepSpeed was detected and loaded for use. + + #### **deepspeed_enabled** + > Indicates whether DeepSpeed is currently enabled or disabled for the current TTS engine, assuming the above two are true. + + #### **generationspeed_capable** + > Indicates if the current TTS engine supports generating TTS at different speeds. + + #### **generationspeed_set** + > Shows the current default speed set in the TTS engine's configuration settings. + + #### **lowvram_capable** + > Indicates if the current TTS engine allows moving the TTS model between VRAM and System RAM, which is useful in low VRAM situations, such as when using AllTalk with an LLM that fills up VRAM. This helps keep models contiguous within VRAM and speeds up processing. + + #### **lowvram_enabled** + > If the model is lowvram_capable, setting this to true allows the model to move between VRAM and System RAM as necessary. + + #### **pitch_capable** + > Indicates if the current TTS engine supports generating TTS at different pitches. + + #### **pitch_set** + > Shows the current default audio pitch set in the TTS engine's configuration settings. + + #### **repetitionpenalty_capable** + > Indicates if the current TTS engine supports different repetition penalties for its TTS models. + + #### **repetitionpenalty_set** + > Shows the current default repetition penalty set in the TTS engine's configuration settings. + + #### **temperature_capable** + > Indicates if the current TTS engine supports different temperature settings for its TTS models. + + #### **temperature_set** + > Shows the current default temperature set in the TTS engine's configuration settings. + + #### **languages_capable** + > Indicates if the models within the current TTS engine support multiple languages. This can be used to disable language selection in your interface if necessary. + + #### **multivoice_capable** + > Indicates if the current model loaded into the TTS engine supports multiple voices or just one voice. If a model is not multi-voice capable, you can display 'Default' as the only available voice. + + #### **multimodel_capable** + > Some TTS engines, such as Piper, use models as voices. In such cases, you don't need to load different models but present the models as voices. If a model is not multimodel_capable, it indicates this status in your interface. + + """ + + openapi_endpoint = """ + ### 🟠 OpenAI Speech API Compatible Endpoint
+ Validate and generate audio based on text input using an OpenAI Speech v1 API compatible endpoint, as per the documentation at [OpenAI Text-to-Speech Guide](https://platform.openai.com/docs/guides/text-to-speech) and [OpenAI API Reference](https://platform.openai.com/docs/api-reference/audio). + + Voices are mapped on a one-to-one basis within the AllTalk Gradio interface on a TTS engine by TTS engine basis. If RVC is globally enabled in AllTalk settings and a voice other than "Disabled" is selected for the character voice, the chosen RVC voice will be applied after the TTS is generated and before the audio is transcoded and sent back out. + + Note that there is no capability within the OpenAI API to specify a language, so the response will be in whatever language the currently loaded TTS engine and model support. + +          **URL**: `http://{ipaddress}:{port}/v1/audio/speech`
+          **Method**: `POST`
+ + `curl -X POST "http://127.0.0.1:7851/v1/audio/speech" -H "Content-Type: application/json" -d "{\"model\":\"any_model_name\",\"input\":\"Hello, this is a test.\",\"voice\":\"nova\",\"response_format\":\"wav\",\"speed\":1.0}"` + + The request body must be a JSON object with the following fields: + +         **model** (string): The TTS model to use. Currently ignored, but is required in the request.
+         **input** (string): The text to generate audio for. Maximum length is 4096 characters.
+         **voice** (string): The voice to use when generating the audio. Supported voices are ["alloy", "echo", "fable", "nova", "onyx", "shimmer"].
+         **response_format** (string, optional): The format of the audio. Audio will be transcoded to the requested format.
+         **speed** (float, optional): The speed of the generated audio. Must be between 0.25 and 4.0. Default is 1.0. Id the TTS engine loaded doesnt support different speeds, then the audio generated will not abide by this speed request.

+ + ``` + { + "model": "any_model_name", + "input": "Hello, this is a test.", + "voice": "nova", + "response_format": "wav", + "speed": 1.0 + } + ``` + + #### Python Example + ``` + import requests + import json + + # Define the endpoint URL + url = "http://127.0.0.1:7851/v1/audio/speech" + + # Define the request payload + payload = { + "model": "any_model_name", + "input": "Hello, this is a test.", + "voice": "nova", + "response_format": "wav", + "speed": 1.0 + } + + # Set the headers + headers = { + "Content-Type": "application/json" + } + + # Send the POST request + response = requests.post(url, data=json.dumps(payload), headers=headers) + + # Check the response + if response.status_code == 200: + with open("output.wav", "wb") as f: + f.write(response.content) + print("Audio file saved as output.wav") + else: + print(f"Error: {response.status_code} - {response.text}") + ``` + #### Javascript Example + ``` + // Define the endpoint URL + const url = "http://127.0.0.1:7851/v1/audio/speech"; + + // Define the request payload + const payload = { + model: "any_model_name", + input: "Hello, this is a test.", + voice: "nova", + response_format: "wav", + speed: 1.0 + }; + + // Set the headers + const headers = { + "Content-Type": "application/json" + }; + + // Send the POST request + fetch(url, { + method: "POST", + headers: headers, + body: JSON.stringify(payload) + }) + .then(response => { + if (response.ok) { + return response.blob(); + } else { + return response.text().then(text => { throw new Error(text); }); + } + }) + .then(blob => { + // Create a link element + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.style.display = 'none'; + a.href = url; + a.download = 'output.wav'; + + // Append the link to the body + document.body.appendChild(a); + + // Programmatically click the link to trigger the download + a.click(); + + // Remove the link from the document + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + + console.log("Audio file saved as output.wav"); + }) + .catch(error => { + console.error("Error:", error); + }); + ``` + + """ + + interaction_endpoints = """ + ### 🟠 Stop Generation Endpoint
+ Interrupt the current TTS generation process. When this endpoint is called, it sets tts_stop_generation in the model_engine to True, which can be used to interrupt the current TTS generation. Stop requests will only be honored if the current TTS engine is capable of handling and can honor a stop request partway through generation. + +          **URL**: `http://{ipaddress}:{port}/api/stop-generation`
+          **Method**: `PUT`
+ + > `curl -X PUT "http://127.0.0.1:7851/api/stop-generation"` + + ``` + { + "message": "Cancelling current TTS generation" + } + ``` + + ### 🟠 Reload Configuration Endpoint
+ Reload the TTS engine's configuration and scan for new voices and models. When this endpoint is called, it reloads all current settings from the configuration files and scans for new voices and models that are available. This ensures that subsequent calls to /api/currentsettings, /api/voices, and /api/rvcvoices return an up-to-date set of settings and information about the current state of the TTS engine. + +          **URL**: `http://{ipaddress}:{port}/api/reload_config`
+          **Method**: `GET`
+ + > `curl -X GET "http://127.0.0.1:7851/api/reload_config"` + + `Config file reloaded successfully` + + ### 🟠 Reload/Swap model
+ Can be used to load in/swap to one of the models presented by /api/currentsettings in the `models_available` list. + +          **URL**: `http://{ipaddress}:{port}/api/reload?tts_method=x`
+          **Method**: `POST`
+ + > `curl -X POST "http://127.0.0.1:7851/api/reload?tts_method=xtts%20-%20xttsv2_2.0.2"` + + {"status": "model-success"} or {"status": "model-failure"} + + ### 🟠 Switch DeepSpeed Endpoint
+ +          **URL**: `http://{ipaddress}:{port}/api/deepspeed`
+          **Method**: `POST`
+ + > `curl -X POST "http://127.0.0.1:7851/api/deepspeed?new_deepspeed_value=True"` + + Replace True with False to disable DeepSpeed mode. + + ``` + { + "status": "deepspeed-success" + } + ``` + + ### 🟠 Switching Low VRAM Endpoint
+ +          **URL**: `http://{ipaddress}:{port}/api/lowvramsetting`
+          **Method: `POST`
+ + > `curl -X POST "http://127.0.0.1:7851/api/lowvramsetting?new_low_vram_value=True"` + + Replace True with False to disable Low VRAM mode. + + ``` + { + "status": "lowvram-success" + } + ``` + """ + + standardtts_endpoint = """ + ### 🟠 TTS Generation Endpoint (Standard Generation)
+ Generate TTS audio based on text input. This endpoint supports both character and narrator speech generation. All global settings for the API endpoint can be configured within the AllTalk interface under Global Settings > AllTalk API Defaults. TTS engine-specific settings, such as voices to use or engine parameters, can be set on an engine-by-engine basis in TTS Engine Settings > TTS Engine of your choice.
+ + It's important to note that although you can send all these variables/settings, the loaded TTS engine will only support them if it is capable e.g. you can request a TTS generation in Russian, but if the TTS model that is loaded in only support English, it's only going to generate english sounding text to speech.
+ +          **URL**: `http://{ipaddress}:{port}/api/tts-generate`
+          **Method**: `POST`
+          **Content-Type**: application/x-www-form-urlencoded
+ + ### 🟠 Example Command Lines (Standard Generation) + Standard TTS generation supports narration and generates a WAV file/blob. + + > **Standard TTS Speech Example** - Generate a time-stamped file for standard text:

+ `curl -X POST "http://127.0.0.1:7851/api/tts-generate" -d "text_input=All of this is text spoken by the character. This is text not inside quotes, though that doesnt matter in the slightest" -d "text_filtering=standard" -d "character_voice_gen=female_01.wav" -d "narrator_enabled=false" -d "narrator_voice_gen=male_01.wav" -d "text_not_inside=character" -d "language=en" -d "output_file_name=myoutputfile" -d "output_file_timestamp=true" -d "autoplay=true" -d "autoplay_volume=0.8"` + + > **Narrator Example** - Generate a time-stamped file for text with narrator and character speech:

+ `curl -X POST "http://127.0.0.1:7851/api/tts-generate" -d "text_input=*This is text spoken by the narrator* \\\"This is text spoken by the character\\\;". This is text not inside quotes." -d "text_filtering=standard" -d "character_voice_gen=female_01.wav" -d "narrator_enabled=true" -d "narrator_voice_gen=male_01.wav" -d "text_not_inside=character" -d "language=en" -d "output_file_name=myoutputfile" -d "output_file_timestamp=true" -d "autoplay=true" -d "autoplay_volume=0.8"`
+ + > If your text contains double quotes, escape them with \" (see the narrator example).
+ Voices sent in the request have to match the voices available within the TTS engine loaded. Generation requests where the voices dont match, will result nothing being generated and possibly an error message. + + Because of the way AllTalk works you can send a request with any mix of settings you wish e.g. + + > `curl -X POST "http://127.0.0.1:7851/api/tts-generate" -d "text_input=All of this is text spoken by the character. This is text not inside quotes, though that doesnt matter in the slightest"` + + Would generate the TTS for whatver engine is curretly loaded in. It will use the default API settings and default TTS engine settings to populate any missing fields, which gives you flexibility on how you want to send API requests to AllTalk. + + ### 🟠 Request Parameters + #### **text_input** + > The text you want the TTS engine to produce. Use escaped double quotes for character speech and asterisks for narrator speech if using the narrator function. Example:

+         -d "text_input=*This is text spoken by the narrator* \"This is text spoken by the character\". This is text not inside quotes." + + #### **text_filtering** + > Filter for text. Options:

+         **none**: No filtering. Raw text is sent to the TTS engine.
+         **standard**: Basic level of filtering to clean up special characters.
+         **html**: For HTML content with entities like ".

+         -d "text_filtering=none"
+         -d "text_filtering=standard"
+         -d "text_filtering=html"
+ + #### **character_voice_gen** + > The name of the character's voice file (WAV format).

+         -d "character_voice_gen=female_01.wav" + + #### **rvccharacter_voice_gen** + > The name of the RVC voice file for the character. Should be in the format `folder\\file.pth` or the word `Disabled`. When Disabled is sent and RVC is globally enabled, the RVC pipeline will not be used for the character/main voice.

+         -d "rvccharacter_voice_gen=folder\\voice1.pth" + + #### **narrator_enabled** + > Enable or disable the narrator function. If true, minimum text filtering is set to standard.

+         -d "narrator_enabled=true"
+         -d "narrator_enabled=false" + + #### **narrator_voice_gen** + > The name of the narrator's voice file (WAV format).

+         -d "narrator_voice_gen=male_01.wav" + + #### **rvcnarrator_voice_gen** + > The name of the RVC voice file for the narrator. Should be in the format folder\\file.pth or the word Disabled. When Disabled is sent and RVC is globally enabled, the RVC pipeline will not be used for the character/main voice.

+         -d "rvcnarrator_voice_gen=folder\\voice2.pth" + + #### **text_not_inside** + > Specify the handling of lines not inside double quotes or asterisks for the narrator feature. Options:

+         **character**: Treat as character speech.
+         **narrator**: Treat as narrator speech.
+         **silent**: Ignore text not inside quotes or asterisks.

+         -d "text_not_inside=character"
+         -d "text_not_inside=narrator"
+         -d "text_not_inside=silent"
+ + #### **language** + > Choose the language for TTS. Options:

+         **ar**: Arabic
+         **zh-cn**: Chinese (Simplified)
+         **cs**: Czech
+         **nl**: Dutch
+         **en**: English
+         **fr**: French
+         **de**: German
+         **hi**: Hindi (limited support)
+         **hu**: Hungarian
+         **it**: Italian
+         **ja**: Japanese
+         **ko**: Korean
+         **pl**: Polish
+         **pt**: Portuguese
+         **ru**: Russian
+         **es**: Spanish
+         **tr**: Turkish

+         -d "language=en" + + #### **output_file_name*** + > The name of the output file (excluding the .wav extension).

+         --d "output_file_name=myoutputfile" + + #### **output_file_timestamp** + > Add a timestamp to the output file name. If true, each file will have a unique timestamp; otherwise, the same file name will be overwritten each time you generate TTS.

+         --d "output_file_timestamp=true"
+         --d "output_file_timestamp=false" + + #### **autoplay** + > Enable or disable playing the generated TTS to your standard sound output device at the time of TTS generation.

+         --d "autoplay=true"
+         --d "autoplay=false" + + #### **autoplay_volume** + > Set the autoplay volume. Should be between 0.1 and 1.0.

+         --d "autoplay_volume=0.8" + + #### **speed** + > Set the speed of the generated audio. Should be between 0.25 and 2.0.

+         --d "speed=1.0" + + #### **pitch** + > Set the pitch of the generated audio. Should be between -10 and 10.

+         --d "pitch=0" + + #### **temperature** + > Set the temperature for the TTS engine. Should be between 0.1 and 1.0.

+         --d "temperature=0.75" + + #### **repetition_penalty** + > Set the repetition penalty for the TTS engine. Should be between 1.0 and 20.0.

+         --d "repetition_penalty=10" + + ### 🟠 TTS Generation Response + > The API returns a JSON object with the following properties:

+         **status**: Indicates whether the generation was successful (generate-success) or failed (generate-failure).
+         **output_file_path**: The on-disk location of the generated WAV file.
+         **output_file_url**: The HTTP location for accessing the generated WAV file for browser playback.
+         **output_cache_url**: The HTTP location for accessing the generated WAV file as a pushed download.
+ + ``` + { + "status": "generate-success", + "output_file_path": "C:\\text-generation-webui\\extensions\\alltalk_tts\\outputs\\myoutputfile_1704141936.wav", + "output_file_url": "/audio/myoutputfile_1704141936.wav", + "output_cache_url": "/audiocache/myoutputfile_1704141936.wav" + } + ``` +
+ Note: The response no longer includes the IP address and port number. You will need to add these in your own software/extension. + """ + + streamingtts_endpoint = """ + ### 🟠 TTS Generation Endpoint (Streaming Generation) + Generate TTS audio as a stream. This endpoint does not support narration and will generate an audio stream, not a file. It also does not support the RVC pipeline. Generate and stream TTS audio directly for real-time playback. + +          **URL**: `http://{ipaddress}:{port}/api/tts-generate-streaming`
+          **Method**: POST
+          **Content-Type**: application/x-www-form-urlencoded
+ + > `http://127.0.0.1:7851/api/tts-generate-streaming?text=Here%20is%20some%20text&voice=female_01.wav&language=en&output_file=stream_output.wav` + + Example JavaScript for Streaming Playback: + + ``` + const text = "Here is some text"; + const voice = "female_01.wav"; + const language = "en"; + const outputFile = "stream_output.wav"; + const encodedText = encodeURIComponent(text); + const streamingUrl = `http://localhost:7851/api/tts-generate-streaming?text=${encodedText}&voice=${voice}&language=${language}&output_file=${outputFile}`; + const audioElement = new Audio(streamingUrl); + audioElement.play(); + ``` + + Response: A StreamingResponse for the audio stream. + + Request Parameters: + +         **text**: The text to convert to speech.
+         **voice**: The voice type to use.
+         **language**: The language for the TTS.
+         **output_file**: The name of the output file.
+         **Response**: A JSON object indicating the status of the request. + + ``` + { + "output_file_path": "stream_output.wav" + } + ``` + """ + + with gr.Tab("API Endpoints & Dev"): + with gr.Tab("Best Development Practices"): gr.Markdown(best_practices) + with gr.Tab("Standard TTS Generation"): gr.Markdown(standardtts_endpoint) + with gr.Tab("Streaming TTS Generation"): gr.Markdown(streamingtts_endpoint) + with gr.Tab("Settings API's"): gr.Markdown(settings_endpoints) + with gr.Tab("Interaction API's"): gr.Markdown(interaction_endpoints) + with gr.Tab("OpenAPI Compatible Endpoint"): gr.Markdown(openapi_endpoint) \ No newline at end of file diff --git a/system/gradio_pages/themes/loadThemes.py b/system/gradio_pages/themes/loadThemes.py new file mode 100644 index 0000000..e99ced9 --- /dev/null +++ b/system/gradio_pages/themes/loadThemes.py @@ -0,0 +1,114 @@ +import os +import json +import sys +import importlib + +# Hard-coded paths +this_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..")) +config_file = os.path.join(this_folder, "confignew.json") +gradio_pages_folder = os.path.join(this_folder, "system", "gradio_pages", "themes") +theme_list_file = os.path.join(gradio_pages_folder, "theme_list.json") +themes_file_path = os.path.join(gradio_pages_folder, "themes.py") + +# Add gradio_pages folder to the system path +sys.path.append(gradio_pages_folder) + +def get_class(filename): + with open(filename, "r", encoding="utf8") as file: + for line in file: + if "class " in line: + return line.split("class ")[1].split(":")[0].split("(")[0].strip() + return None + + +def get_list(): + themes_from_files = [ + os.path.splitext(name)[0] + for name in os.listdir(gradio_pages_folder) + if name.endswith(".py") + ] + try: + with open(theme_list_file, "r", encoding="utf8") as json_file: + themes_from_url = [item["id"] for item in json.load(json_file)] + except FileNotFoundError: + themes_from_url = [] + + combined_themes = sorted(set(themes_from_files + themes_from_url)) + return list(combined_themes) + + +def select_theme(name): + selected_file = name + ".py" + full_path = os.path.join(gradio_pages_folder, selected_file) + + try: + with open(config_file, "r", encoding="utf8") as json_file: + config_data = json.load(json_file) + + if not os.path.exists(full_path): + # If the theme is from theme_list.json, update only the class field + config_data["theme"]["file"] = None + config_data["theme"]["class"] = name + message = f"Theme {name} successfully selected, restart AllTalk." + else: + # If the theme file exists locally, update both file and class fields + class_found = get_class(full_path) + if class_found: + config_data["theme"]["file"] = selected_file + config_data["theme"]["class"] = class_found + message = f"Theme {name} successfully selected, restart AllTalk." + else: + message = f"Theme {name} was not found." + + with open(config_file, "w", encoding="utf8") as json_file: + json.dump(config_data, json_file, indent=2) + + print(message) + return message + + except Exception as e: + print(f"[AllTalk TTS] Error selecting theme {name}: {str(e)}") + return message + +def read_json(): + try: + with open(config_file, "r", encoding="utf8") as json_file: + data = json.load(json_file) + selected_file = data["theme"]["file"] + class_name = data["theme"]["class"] + + if selected_file and class_name: + return class_name + elif not selected_file and class_name: + return class_name + else: + return "gradio/base" + except Exception as e: + print(f"[AllTalk TTS] Error reading config.json: {e}") + return "gradio/base" + + +def load_json(): + try: + with open(config_file, "r", encoding="utf8") as json_file: + data = json.load(json_file) + selected_file = data["theme"]["file"] + class_name = data["theme"]["class"] + + if selected_file and class_name: + module_name = os.path.splitext(selected_file)[0] + module_spec = importlib.util.spec_from_file_location(module_name, themes_file_path) + module = importlib.util.module_from_spec(module_spec) + module_spec.loader.exec_module(module) + obtained_class = getattr(module, class_name) + instance = obtained_class() + print(f"Theme Loaded: {class_name}") + return instance + elif not selected_file and class_name: + return class_name + else: + print("The theme is incorrect.") + return None + except Exception as e: + print(f"Error Loading: {str(e)}") + return None diff --git a/system/gradio_pages/themes/theme_list.json b/system/gradio_pages/themes/theme_list.json new file mode 100644 index 0000000..8efc6f9 --- /dev/null +++ b/system/gradio_pages/themes/theme_list.json @@ -0,0 +1,81 @@ +[ + {"id": "freddyaboulton/dracula_revamped"}, + {"id": "freddyaboulton/bad-theme-space"}, + {"id": "gradio/dracula_revamped"}, + {"id": "abidlabs/dracula_revamped"}, + {"id": "gradio/dracula_test"}, + {"id": "abidlabs/dracula_test"}, + {"id": "gradio/seafoam"}, + {"id": "gradio/glass"}, + {"id": "gradio/monochrome"}, + {"id": "gradio/soft"}, + {"id": "gradio/default"}, + {"id": "gradio/base"}, + {"id": "abidlabs/pakistan"}, + {"id": "dawood/microsoft_windows"}, + {"id": "ysharma/steampunk"}, + {"id": "ysharma/huggingface"}, + {"id": "gstaff/xkcd"}, + {"id": "JohnSmith9982/small_and_pretty"}, + {"id": "abidlabs/Lime"}, + {"id": "freddyaboulton/this-theme-does-not-exist-2"}, + {"id": "aliabid94/new-theme"}, + {"id": "aliabid94/test2"}, + {"id": "aliabid94/test3"}, + {"id": "aliabid94/test4"}, + {"id": "abidlabs/banana"}, + {"id": "freddyaboulton/test-blue"}, + {"id": "gstaff/sketch"}, + {"id": "gstaff/whiteboard"}, + {"id": "ysharma/llamas"}, + {"id": "abidlabs/font-test"}, + {"id": "YenLai/Superhuman"}, + {"id": "bethecloud/storj_theme"}, + {"id": "sudeepshouche/minimalist"}, + {"id": "knotdgaf/gradiotest"}, + {"id": "ParityError/Interstellar"}, + {"id": "ParityError/Anime"}, + {"id": "Ajaxon6255/Emerald_Isle"}, + {"id": "ParityError/LimeFace"}, + {"id": "finlaymacklon/smooth_slate"}, + {"id": "finlaymacklon/boxy_violet"}, + {"id": "derekzen/stardust"}, + {"id": "EveryPizza/Cartoony-Gradio-Theme"}, + {"id": "Ifeanyi/Cyanister"}, + {"id": "Tshackelton/IBMPlex-DenseReadable"}, + {"id": "snehilsanyal/scikit-learn"}, + {"id": "Himhimhim/xkcd"}, + {"id": "shivi/calm_seafoam"}, + {"id": "nota-ai/theme"}, + {"id": "rawrsor1/Everforest"}, + {"id": "SebastianBravo/simci_css"}, + {"id": "rottenlittlecreature/Moon_Goblin"}, + {"id": "abidlabs/test-yellow"}, + {"id": "abidlabs/test-yellow3"}, + {"id": "idspicQstitho/dracula_revamped"}, + {"id": "kfahn/AnimalPose"}, + {"id": "HaleyCH/HaleyCH_Theme"}, + {"id": "simulKitke/dracula_test"}, + {"id": "braintacles/CrimsonNight"}, + {"id": "wentaohe/whiteboardv2"}, + {"id": "reilnuud/polite"}, + {"id": "remilia/Ghostly"}, + {"id": "Franklisi/darkmode"}, + {"id": "coding-alt/soft"}, + {"id": "xiaobaiyuan/theme_land"}, + {"id": "step-3-profit/Midnight-Deep"}, + {"id": "xiaobaiyuan/theme_demo"}, + {"id": "Taithrah/Minimal"}, + {"id": "Insuz/SimpleIndigo"}, + {"id": "zkunn/Alipay_Gradio_theme"}, + {"id": "Insuz/Mocha"}, + {"id": "xiaobaiyuan/theme_brief"}, + {"id": "Ama434/434-base-Barlow"}, + {"id": "Ama434/def_barlow"}, + {"id": "Ama434/neutral-barlow"}, + {"id": "dawood/dracula_test"}, + {"id": "nuttea/Softblue"}, + {"id": "BlueDancer/Alien_Diffusion"}, + {"id": "naughtondale/monochrome"}, + {"id": "Dagfinn1962/standard"} +] \ No newline at end of file diff --git a/system/gradio_pages/themes/themes.py b/system/gradio_pages/themes/themes.py new file mode 100644 index 0000000..33bc0c9 --- /dev/null +++ b/system/gradio_pages/themes/themes.py @@ -0,0 +1,284 @@ +from __future__ import annotations + +from typing import Iterable +import gradio as gr + +# gr.themes.builder() +from gradio.themes.base import Base +from gradio.themes.utils import colors, fonts, sizes +import time + + +class alltalk(Base): + def __init__( + self, + *, + primary_hue: colors.Color | str = colors.neutral, + secondary_hue: colors.Color | str = colors.neutral, + neutral_hue: colors.Color | str = colors.neutral, + spacing_size: sizes.Size | str = sizes.spacing_md, + radius_size: sizes.Size | str = sizes.radius_md, + text_size: sizes.Size | str = sizes.text_lg, + font: fonts.Font | str | Iterable[fonts.Font | str] = ( + "Syne V", + fonts.GoogleFont("Syne"), + "ui-sans-serif", + "system-ui", + ), + font_mono: fonts.Font | str | Iterable[fonts.Font | str] = ( + "ui-monospace", + fonts.GoogleFont("Nunito Sans"), + ), + ): + super().__init__( + primary_hue=primary_hue, + secondary_hue=secondary_hue, + neutral_hue=neutral_hue, + spacing_size=spacing_size, + radius_size=radius_size, + text_size=text_size, + font=font, + font_mono=font_mono, + ) + self.name = ("alltalk",) + self.secondary_100 = ("#dbeafe",) + self.secondary_200 = ("#bfdbfe",) + self.secondary_300 = ("#93c5fd",) + self.secondary_400 = ("#60a5fa",) + self.secondary_50 = ("#eff6ff",) + self.secondary_500 = ("#3b82f6",) + self.secondary_600 = ("#2563eb",) + self.secondary_700 = ("#1d4ed8",) + self.secondary_800 = ("#1e40af",) + self.secondary_900 = ("#1e3a8a",) + self.secondary_950 = ("#1d3660",) + + super().set( + # Blaise + background_fill_primary="#110F0F", + background_fill_primary_dark="#110F0F", + background_fill_secondary="#110F0F", + background_fill_secondary_dark="#110F0F", + block_background_fill="*neutral_800", + block_background_fill_dark="*neutral_800", + block_border_color="*border_color_primary", + block_border_color_dark="*border_color_primary", + block_border_width="1px", + block_border_width_dark="1px", + block_info_text_color="*body_text_color_subdued", + block_info_text_color_dark="*body_text_color_subdued", + block_info_text_size="*text_sm", + block_info_text_weight="400", + block_label_background_fill="*background_fill_primary", + block_label_background_fill_dark="*background_fill_secondary", + block_label_border_color="*border_color_primary", + block_label_border_color_dark="*border_color_primary", + block_label_border_width="1px", + block_label_border_width_dark="1px", + block_label_margin="0", + block_label_padding="*spacing_sm *spacing_lg", + block_label_radius="calc(*radius_lg - 1px) 0 calc(*radius_lg - 1px) 0", + block_label_right_radius="0 calc(*radius_lg - 1px) 0 calc(*radius_lg - 1px)", + block_label_shadow="*block_shadow", + block_label_text_color="*#110F0F", + block_label_text_color_dark="*#110F0F", + block_label_text_weight="400", + block_padding="*spacing_xl", + block_radius="*radius_md", + block_shadow="none", + block_shadow_dark="none", + block_title_background_fill="rgb(255,255,255)", + block_title_background_fill_dark="rgb(255,255,255)", + block_title_border_color="none", + block_title_border_color_dark="none", + block_title_border_width="0px", + block_title_padding="*block_label_padding", + block_title_radius="*block_label_radius", + block_title_text_color="#110F0F", + block_title_text_color_dark="#110F0F", + block_title_text_size="*text_md", + block_title_text_weight="600", + body_background_fill="#110F0F", + body_background_fill_dark="#110F0F", + body_text_color="white", + body_text_color_dark="white", + body_text_color_subdued="*neutral_400", + body_text_color_subdued_dark="*neutral_400", + body_text_size="*text_md", + body_text_weight="400", + border_color_accent="*neutral_600", + border_color_accent_dark="*neutral_600", + border_color_primary="*neutral_800", + border_color_primary_dark="*neutral_800", + button_border_width="*input_border_width", + button_border_width_dark="*input_border_width", + button_cancel_background_fill="*button_secondary_background_fill", + button_cancel_background_fill_dark="*button_secondary_background_fill", + button_cancel_background_fill_hover="*button_cancel_background_fill", + button_cancel_background_fill_hover_dark="*button_cancel_background_fill", + button_cancel_border_color="*button_secondary_border_color", + button_cancel_border_color_dark="*button_secondary_border_color", + button_cancel_border_color_hover="*button_cancel_border_color", + button_cancel_border_color_hover_dark="*button_cancel_border_color", + button_cancel_text_color="#110F0F", + button_cancel_text_color_dark="#110F0F", + button_cancel_text_color_hover="#110F0F", + button_cancel_text_color_hover_dark="#110F0F", + button_large_padding="*spacing_lg calc(2 * *spacing_lg)", + button_large_radius="*radius_lg", + button_large_text_size="*text_lg", + button_large_text_weight="600", + button_primary_background_fill="*primary_600", + button_primary_background_fill_dark="*primary_600", + button_primary_background_fill_hover="*primary_500", + button_primary_background_fill_hover_dark="*primary_500", + button_primary_border_color="*primary_500", + button_primary_border_color_dark="*primary_500", + button_primary_border_color_hover="*primary_400", + button_primary_border_color_hover_dark="*primary_400", + button_primary_text_color="white", + button_primary_text_color_dark="white", + button_primary_text_color_hover="#110F0F", + button_primary_text_color_hover_dark="#110F0F", + button_secondary_background_fill="transparent", + button_secondary_background_fill_dark="transparent", + button_secondary_background_fill_hover="*neutral_800", + button_secondary_background_fill_hover_dark="*neutral_800", + button_secondary_border_color="*neutral_700", + button_secondary_border_color_dark="*neutral_700", + button_secondary_border_color_hover="*neutral_600", + button_secondary_border_color_hover_dark="*neutral_600", + button_secondary_text_color="white", + button_secondary_text_color_dark="white", + button_secondary_text_color_hover="*button_secondary_text_color", + button_secondary_text_color_hover_dark="*button_secondary_text_color", + button_shadow="none", + button_shadow_active="*shadow_inset", + button_shadow_hover="none", + button_small_padding="*spacing_sm calc(2 * *spacing_sm)", + button_small_radius="*radius_lg", + button_small_text_size="*text_md", + button_small_text_weight="400", + button_transition="0.3s ease all", + checkbox_background_color="*neutral_700", + checkbox_background_color_dark="*neutral_700", + checkbox_background_color_focus="*checkbox_background_color", + checkbox_background_color_focus_dark="*checkbox_background_color", + checkbox_background_color_hover="*checkbox_background_color", + checkbox_background_color_hover_dark="*checkbox_background_color", + checkbox_background_color_selected="*secondary_600", + checkbox_background_color_selected_dark="*secondary_600", + checkbox_border_color="*neutral_700", + checkbox_border_color_dark="*neutral_700", + checkbox_border_color_focus="*secondary_500", + checkbox_border_color_focus_dark="*secondary_500", + checkbox_border_color_hover="*neutral_600", + checkbox_border_color_hover_dark="*neutral_600", + checkbox_border_color_selected="*secondary_600", + checkbox_border_color_selected_dark="*secondary_600", + checkbox_border_radius="*radius_sm", + checkbox_border_width="*input_border_width", + checkbox_border_width_dark="*input_border_width", + checkbox_check="url(\"data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e\")", + checkbox_label_background_fill="transparent", + checkbox_label_background_fill_dark="transparent", + checkbox_label_background_fill_hover="transparent", + checkbox_label_background_fill_hover_dark="transparent", + checkbox_label_background_fill_selected="transparent", + checkbox_label_background_fill_selected_dark="transparent", + checkbox_label_border_color="transparent", + checkbox_label_border_color_dark="transparent", + checkbox_label_border_color_hover="transparent", + checkbox_label_border_color_hover_dark="transparent", + checkbox_label_border_width="transparent", + checkbox_label_border_width_dark="transparent", + checkbox_label_gap="*spacing_lg", + checkbox_label_padding="*spacing_md calc(2 * *spacing_md)", + checkbox_label_shadow="none", + checkbox_label_text_color="*body_text_color", + checkbox_label_text_color_dark="*body_text_color", + checkbox_label_text_color_selected="*checkbox_label_text_color", + checkbox_label_text_color_selected_dark="*checkbox_label_text_color", + checkbox_label_text_size="*text_md", + checkbox_label_text_weight="400", + checkbox_shadow="*input_shadow", + color_accent="*primary_500", + color_accent_soft="*primary_50", + color_accent_soft_dark="*neutral_700", + container_radius="*radius_xl", + embed_radius="*radius_lg", + error_background_fill="*background_fill_primary", + error_background_fill_dark="*background_fill_primary", + error_border_color="*border_color_primary", + error_border_color_dark="*border_color_primary", + error_border_width="1px", + error_border_width_dark="1px", + error_text_color="#ef4444", + error_text_color_dark="#ef4444", + form_gap_width="0px", + input_background_fill="*neutral_900", + input_background_fill_dark="*neutral_900", + input_background_fill_focus="*secondary_600", + input_background_fill_focus_dark="*secondary_600", + input_background_fill_hover="*input_background_fill", + input_background_fill_hover_dark="*input_background_fill", + input_border_color="*neutral_700", + input_border_color_dark="*neutral_700", + input_border_color_focus="*secondary_600", + input_border_color_focus_dark="*primary_600", + input_border_color_hover="*input_border_color", + input_border_color_hover_dark="*input_border_color", + input_border_width="1px", + input_border_width_dark="1px", + input_padding="*spacing_xl", + input_placeholder_color="*neutral_500", + input_placeholder_color_dark="*neutral_500", + input_radius="*radius_lg", + input_shadow="none", + input_shadow_dark="none", + input_shadow_focus="*input_shadow", + input_shadow_focus_dark="*input_shadow", + input_text_size="*text_md", + input_text_weight="400", + layout_gap="*spacing_xxl", + link_text_color="*secondary_500", + link_text_color_active="*secondary_500", + link_text_color_active_dark="*secondary_500", + link_text_color_dark="*secondary_500", + link_text_color_hover="*secondary_400", + link_text_color_hover_dark="*secondary_400", + link_text_color_visited="*secondary_600", + link_text_color_visited_dark="*secondary_600", + loader_color="*color_accent", + loader_color_dark="*color_accent", + panel_background_fill="*background_fill_secondary", + panel_background_fill_dark="*background_fill_secondary", + panel_border_color="*border_color_primary", + panel_border_color_dark="*border_color_primary", + panel_border_width="1px", + panel_border_width_dark="1px", + prose_header_text_weight="600", + prose_text_size="*text_md", + prose_text_weight="400", + radio_circle="url(\"data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e\")", + section_header_text_size="*text_md", + section_header_text_weight="400", + shadow_drop="rgba(0,0,0,0.05) 0px 1px 2px 0px", + shadow_drop_lg="0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)", + shadow_inset="rgba(0,0,0,0.05) 0px 2px 4px 0px inset", + shadow_spread="3px", + shadow_spread_dark="1px", + slider_color="#9E9E9E", + slider_color_dark="#9E9E9E", + stat_background_fill="*primary_500", + stat_background_fill_dark="*primary_500", + table_border_color="*neutral_700", + table_border_color_dark="*neutral_700", + table_even_background_fill="*neutral_950", + table_even_background_fill_dark="*neutral_950", + table_odd_background_fill="*neutral_900", + table_odd_background_fill_dark="*neutral_900", + table_radius="*radius_lg", + table_row_focus="*color_accent_soft", + table_row_focus_dark="*color_accent_soft", + ) diff --git a/system/openaittstest.html b/system/openaittstest.html new file mode 100644 index 0000000..c212edf --- /dev/null +++ b/system/openaittstest.html @@ -0,0 +1,180 @@ + + + + + + OpenAI API/AllTalk TTS API Test + + + +
+

OpenAI API/AllTalk TTS API Test

+
+
+ + +
+
+ + +
+
+
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+
+
+ + +

+ +

+
+
+
+ + + + diff --git a/system/requirements/requirements_standalone.txt b/system/requirements/requirements_standalone.txt new file mode 100644 index 0000000..6aaebc0 --- /dev/null +++ b/system/requirements/requirements_standalone.txt @@ -0,0 +1,32 @@ +coqui-tts>=0.24.1 +faster-whisper>=1.0.* +fuzzywuzzy +gradio>=4.26.0 +importlib_metadata>=7.1.* +inputimeout>=1.0.4 +Jinja2>=3.1.* +librosa>=0.10.2.post1 +local_attention>=1.9.* +nvidia-cublas-cu11>=11.11.3.6 +nvidia-cudnn-cu11>=9.1.1.17 +onnxruntime-gpu>=1.18.* +pydantic>=2.7.* +python-ffmpeg>=2.0.* +python-Levenshtein>=0.25.1 +praat-parselmouth>=0.4.* +pyworld>=0.3.* +sounddevice>=0.4.7 +soundfile>=0.12.* +spacy>=3.7.1 +torchcrepe>=0.0.2 +tqdm>=4.66.* +unidic-lite>=1.0.8 +uvicorn>=0.29.0 +pypinyin +hangul_romanize +git+https://github.com/huggingface/parler-tts.git +./system/config/fairseq-0.12.4-cp311-cp311-win_amd64.whl; sys_platform == "win32" +./system/config/fairseq-0.12.3.1-cp311-cp311-linux_x86_64.whl; sys_platform == "linux" +piper-tts; sys_platform == "linux" +piper-tts; sys_platform == "darwin" +piper-phonemize==1.1.0; sys_platform == "darwin" diff --git a/system/requirements/requirements_textgen.txt b/system/requirements/requirements_textgen.txt new file mode 100644 index 0000000..3bd53b5 --- /dev/null +++ b/system/requirements/requirements_textgen.txt @@ -0,0 +1,32 @@ +coqui-tts>=0.24.1 +faster-whisper>=1.0.* +fuzzywuzzy +gradio>=4.26.0 +importlib_metadata>=7.1.* +inputimeout>=1.0.4 +Jinja2>=3.1.* +librosa>=0.10.2.post1 +local_attention>=1.9.* +nvidia-cublas-cu11>=11.11.3.6 +nvidia-cudnn-cu11>=9.1.1.17 +onnxruntime-gpu>=1.18.* +pydantic>=2.7.* +python-ffmpeg>=2.0.* +python-Levenshtein>=0.25.1 +praat-parselmouth>=0.4.* +pyworld>=0.3.* +sounddevice>=0.4.7 +soundfile>=0.12.* +spacy>=3.7.1 +torchcrepe>=0.0.2 +tqdm>=4.66.* +unidic-lite>=1.0.8 +uvicorn>=0.29.0 +pypinyin +hangul_romanize +git+https://github.com/huggingface/parler-tts.git +./system/config/fairseq-0.12.4-cp311-cp311-win_amd64.whl; sys_platform == "win32" +./system/config/fairseq-0.12.3.1-cp311-cp311-linux_x86_64.whl; sys_platform == "linux" +piper-tts; sys_platform == "linux" +piper-tts; sys_platform == "darwin" +piper-phonemize==1.1.0; sys_platform == "darwin" \ No newline at end of file diff --git a/system/requirements/requirements_textgen2.txt b/system/requirements/requirements_textgen2.txt new file mode 100644 index 0000000..ab8688e --- /dev/null +++ b/system/requirements/requirements_textgen2.txt @@ -0,0 +1,4 @@ +jsonlines>=4.0.0 +networkx>=3.0.0 +protobuf==4.25.3 +typing_extensions>=4.9.0 \ No newline at end of file diff --git a/system/tts_diff/tts_diff.py b/system/tts_diff/tts_diff.py new file mode 100644 index 0000000..55371dc --- /dev/null +++ b/system/tts_diff/tts_diff.py @@ -0,0 +1,269 @@ +import json +import argparse +from pathlib import Path +import os +import glob +import platform +import site +import subprocess +import sys +# version 0.8 +os.environ['KMP_DUPLICATE_LIB_OK']='True' + +def find_files_in_path_with_wildcard(pattern): + # Get the site-packages directory of the current Python environment + site_packages_path = site.getsitepackages() + found_paths = [] + # Adjust the sub-directory based on the operating system + sub_directory = "nvidia/cublas" + if platform.system() == "Linux": + sub_directory = os.path.join(sub_directory, "lib") + else: + sub_directory = os.path.join(sub_directory, "bin") + + # Iterate over each site-packages directory (there can be more than one) + for directory in site_packages_path: + # Construct the search directory path + search_directory = os.path.join(directory, sub_directory) + # Use glob to find all files matching the pattern in this directory + for file_path in glob.glob(os.path.join(search_directory, pattern)): + if os.path.isfile(file_path): # Ensure it's a file + found_paths.append(file_path) + return found_paths + +def detect_cublas(): + # Use different file patterns based on the operating system + file_name_pattern = 'cublas64_11.*' if platform.system() == "Windows" else 'libcublas.so.11*' + found_paths = find_files_in_path_with_wildcard(file_name_pattern) + if found_paths: + print("[AllTalk TTSDiff] \033[94mCublas:\033[0m \033[92mDetected\033[0m", found_paths) + else: + print("[AllTalk TTSDiff] \033[94mCublas:\033[0m \033[91mERROR Not Detected\033[0m") + print("[AllTalk TTSDiff] \033[94mPlease install Cublas from the Nvidia CUDA Toolkit \033[0mhttps://developer.nvidia.com/cuda-downloads") + exit(1) + + +parser = argparse.ArgumentParser(description="Compare TTS output with the original text using detailed comparison.") +parser.add_argument("--threshold", type=int, default=98, help="Similarity threshold for considering a match (default: 98)") +parser.add_argument("--ttslistpath", help="Path to the ttsList.json file") +parser.add_argument("--wavfilespath", help="Path to the wav outputs folder") +args = parser.parse_args() + +if not args.ttslistpath: + parser.error("[AllTalk TTSDiff] Please specify the path to the ttslist.json file using the --ttslistpath argument.") +if not args.wavfilespath: + parser.error("[AllTalk TTSDiff] Please specify the path to the wavs output folder file using the --wavfilespath argument.") + +json_file_path = Path(args.ttslistpath).resolve() +wav_file_path = Path(args.wavfilespath).resolve() +accuracy_threshold = args.threshold + +def disclaimer_text(): + print(f"\nDESCRIPTION: ") + print(f" This script is designed to assist in identifying bad TTS generation ID's from the TTS Generator.\n") + print(f" This will download the Whisper Large-v2 model (if not already downloaded), and use it to compare your text to") + print(f" to the generated TTS. If it finds an issue, it will flag the ID number that has the issue. This does NOT 100%") + print(f" ensure that all TTS generated is perfect, sounds good etc, but it should flag up glaring issues and help with") + print(f" the process of identifying bad TTS generations.") + print(f"\nNOTES ON USE: ") + print(f" - Ensure AllTalk is running in its own command prompt/terminal.") + print(f" - This script should be running in its own command prompt/terminal window in the same Python Environment that") + print(f" AllTalk is running in.") + print(f" - After generating your text in the TTS Generator, you need to `Export List to JSON` and save the file in the") + print(f" same folder/directory this script is running from.") + print(f" - You can use `--threshold` with a numerical value from 1 to 100 to set accuracy detection. For example it may") + print(f" detect/transcribe `their` as `there`, which of course, both words sound the same. If you want 100% accuracy") + print(f" you will set 100 as the threshold. You will more than likely want a number in the high 90's. Default is set") + print(f" at 98, which gives a little flexibility.") + print(f" - The JSON file must be named \033[93mttsList.json\033[0m") + print(f" - When you have your ID list, go back into the TTS Generator, correct any lines and regenerate them. If you") + print(f" want to re-test everthing again after re-generating, you will need to export your list again and re-run this") + print(f" script again, against your newly exported JSON list.") + print(f" - This requires access to \033[93mcublas64_11\033[0m, the same as Finetuning.") + print(f" https://github.com/erew123/alltalk_tts/tree/main?#-important-requirements-cuda-118\n") + return + +try: + from faster_whisper import WhisperModel + import re + import string + import spacy + import torch + from fuzzywuzzy import fuzz +except ImportError as e: + print(f"[AllTalk TTSDiff] ERROR STARTING SCRIPT:") + print("[AllTalk TTSDiff] An error occurred importing one or more required libraries.") + print("[AllTalk TTSDiff] Please ensure you have \033[94mactivated your Python environment \033[0mand \033[94minstalled all requirements.\033[0m") + print("[AllTalk TTSDiff] \033[94mMissing library:\033[93m", str(e), "\033[0m") + exit(1) + +# Attempt to load the spaCy model +try: + # Load a medium-sized spaCy model, adjust to "en_core_web_md" or "en_core_web_lg" as needed + nlp = spacy.load("en_core_web_md") +except Exception as e: + print("[AllTalk TTSDiff] Failed to load the spaCy language model.") + + # Attempt to download the spaCy model automatically + try: + print("[AllTalk TTSDiff] Attempting to automatically download the spaCy language model...") + subprocess.run([sys.executable, "-m", "spacy", "download", "en_core_web_md"], check=True) + + # Try to load the model again after downloading + nlp = spacy.load("en_core_web_md") + print("[AllTalk TTSDiff] Model downloaded and loaded successfully.") + except Exception as download_exception: + print("[AllTalk TTSDiff] Automatic download failed.") + print("[AllTalk TTSDiff] Please manually install the spaCy language model by running:") + print("[AllTalk TTSDiff] \033[93mpython -m spacy download en_core_web_md\033[0m") + exit(1) + +def texts_are_similar(text1, text2, threshold=0.8): + # Preliminary check for short texts + if len(text1) < 10 or len(text2) < 10: + ratio = fuzz.ratio(text1, text2) / 100.0 # Use FuzzyWuzzy for short texts + return ratio > threshold + + # Process the texts through the NLP model for longer texts + doc1 = nlp(text1) + doc2 = nlp(text2) + + # Compute semantic similarity + similarity = doc1.similarity(doc2) + + return similarity >= threshold + +def normalize_text(text): + # Normalize or remove CRLF and other non-standard whitespaces + text = text.replace("\r\n", " ").replace("\n", " ").replace("\r", " ") + + # Convert to lowercase + text = text.lower() + + # Standardize and then remove quotation marks + text = text.replace("“", '"').replace("”", '"').replace("‘", "'").replace("’", "'") + text = text.translate(str.maketrans('', '', '"\'')) + + # Remove all other punctuation except hyphens to preserve compound words + text = text.translate(str.maketrans('', '', string.punctuation.replace("-", ""))) + + # Collapse any sequence of whitespace (including spaces) into a single space + text = re.sub(r'\s+', ' ', text) + + return text.strip() + +spoken_punctuation_mapping = { + "dot": ".", + "comma": ",", + # Add more mappings as needed +} + +def contains_spoken_punctuation(transcribed_text): + for spoken, punctuation in spoken_punctuation_mapping.items(): + if spoken in transcribed_text: + return True + return False + +def detailed_comparison(original_text, transcribed_text, threshold=98): + original_clean = normalize_text(original_text) + transcribed_clean = normalize_text(transcribed_text) + ratio = fuzz.ratio(original_clean, transcribed_clean) + return ratio > threshold + +def access_local_file(url): + # Extract the file name from the URL + file_name = url.split('/')[-1] + # Construct the full path to the file on disk + file_path = wav_file_path / file_name + # Check if the file exists + if not file_path.exists(): + print(f"[AllTalk TTSDiff] File does not exist: {file_path}") + return None + return file_path + +def transcribe_and_compare(file_url, original_text, model, item_id, flagged_ids): + audio_file_path = access_local_file(file_url) + if audio_file_path is None: + print(f"[AllTalk TTSDiff] Could not access local file for URL: {file_url}") + return # Skip this file if it can't be accessed + + try: + segments, info = model.transcribe(str(audio_file_path), beam_size=5) + transcribed_text = " ".join([segment.text for segment in segments]) + except Exception as e: + print(f"[AllTalk TTSDiff] Error transcribing file {audio_file_path}: {e}") + return # Skip this file if transcription fails + + segments, info = model.transcribe(str(audio_file_path), beam_size=5) + transcribed_text = " ".join([segment.text for segment in segments]) + + # Normalize texts for comparison + original_text_normalized = normalize_text(original_text) + transcribed_text_normalized = normalize_text(transcribed_text) + + # Enhanced comparison using detailed fuzzy matching + is_detailed_match = detailed_comparison(original_text_normalized, transcribed_text_normalized, args.threshold) + has_spoken_punctuation = contains_spoken_punctuation(transcribed_text_normalized) and not contains_spoken_punctuation(original_text_normalized) + + # Adjust is_match based on detailed comparison and spoken punctuation check + is_match = is_detailed_match and not has_spoken_punctuation + + # Only log and flag IDs for review if there's a mismatch or detected issues + if not is_match: + #print(f"\033[93mMismatch Detected - ID:\033[0m {item_id}") + print(f"[AllTalk TTSDiff] \033[93mText:\033[0m {original_text}") + print(f"[AllTalk TTSDiff] \033[91mTTS :\033[0m{transcribed_text}") + if has_spoken_punctuation: + print(f"[AllTalk TTSDiff] Note: Potential incorrect spoken punctuation detected.") + flagged_ids.append(item_id) # Track the ID for review + +def main(): + print(f"[AllTalk TTSDiff]") + print(f"[AllTalk TTSDiff]") + print("[AllTalk TTSDiff] \033[92mStarting Compare of Text vs TTS:\033[0m") + print("[AllTalk TTSDiff]") + print("[AllTalk TTSDiff] \033[94mJSON file :\033[92m", json_file_path, "\033[0m") + print("[AllTalk TTSDiff] \033[94mWAV files :\033[92m", wav_file_path, "\033[0m") + print("[AllTalk TTSDiff] \033[94mAccuracy :\033[92m", accuracy_threshold, "%", "\033[0m") + detect_cublas() + model_size = "large-v2" + device = "cuda" if torch.cuda.is_available() else "cpu" + model = WhisperModel(model_size, device=device, compute_type="float32") + + flagged_ids = [] # Initialize the list to track IDs needing review + + try: + with open(json_file_path, "r", encoding="utf-8") as f: + tts_list = json.load(f) + except FileNotFoundError: + print(f"[AllTalk TTSDiff] ERROR STARTING SCRIPT:") + print("[AllTalk TTSDiff] Error: \033[93mttsList.json\033[0m file not found.") + print("[AllTalk TTSDiff] Please ensure you follow these steps:") + print("[AllTalk TTSDiff] After generating your text in the TTS Generator, you need to `Export List to JSON` and save the file in the") + print("[AllTalk TTSDiff] same folder/directory this script is running from.") + print("[AllTalk TTSDiff] The JSON file must be named \033[93mttsList.json\033[0m") + exit(1) + + print(f"[AllTalk TTSDiff]") + + for item in tts_list: + print(f"[AllTalk TTSDiff] Processing ID: {item['id']}") + transcribe_and_compare(item['fileUrl'], item['text'], model, item['id'], flagged_ids) + + print(f"[AllTalk TTSDiff]") + # Print summary information at the end + if flagged_ids: + print(f"[AllTalk TTSDiff] \033[94mIDs needing review:\033[0m", ', '.join(map(str, flagged_ids))) + print(f"[AllTalk TTSDiff]") + print(f"[AllTalk TTSDiff] Review ID's and correct any lines by editing & regenerating as necessary.") + else: + print("[AllTalk TTSDiff] \033[94mIDs needing review:\033[0m No issues detected.") + summary_data = { + "flagged_ids": flagged_ids + } + with open(wav_file_path / "analysis_summary.json", "w") as summary_file: + json.dump(summary_data, summary_file) + print("[AllTalk TTSDiff]") + +if __name__ == "__main__": + main() diff --git a/system/tts_engines/new_engines.json b/system/tts_engines/new_engines.json new file mode 100644 index 0000000..abc36b5 --- /dev/null +++ b/system/tts_engines/new_engines.json @@ -0,0 +1,12 @@ +{ + "engines_available": [ + { + "name": "parler", + "selected_model": "these are examples of how you add new engines into the list here. You would put a model name in this line" + }, + { + "name": "xtts", + "selected_model": "xtts - xttsv2_2.0.3" + } + ] +} \ No newline at end of file diff --git a/system/tts_engines/parler/available_models.json b/system/tts_engines/parler/available_models.json new file mode 100644 index 0000000..7135ef7 --- /dev/null +++ b/system/tts_engines/parler/available_models.json @@ -0,0 +1,18 @@ +{ + "first_start_model": "parler_tts_mini_v0.1", + "models": [ + { + "model_name": "parler_tts_mini_v0.1", + "files_to_download": [ + "https://huggingface.co/parler-tts/parler_tts_mini_v0.1/resolve/main/config.json?download=true", + "https://huggingface.co/parler-tts/parler_tts_mini_v0.1/resolve/main/generation_config.json?download=true", + "https://huggingface.co/parler-tts/parler_tts_mini_v0.1/resolve/main/preprocessor_config.json?download=true", + "https://huggingface.co/parler-tts/parler_tts_mini_v0.1/resolve/main/special_tokens_map.json?download=true", + "https://huggingface.co/parler-tts/parler_tts_mini_v0.1/resolve/main/spiece.model?download=true", + "https://huggingface.co/parler-tts/parler_tts_mini_v0.1/resolve/main/tokenizer.json?download=true", + "https://huggingface.co/parler-tts/parler_tts_mini_v0.1/resolve/main/tokenizer_config.json?download=true", + "https://huggingface.co/parler-tts/parler_tts_mini_v0.1/resolve/main/model.safetensors?download=true" + ] + } + ] +} \ No newline at end of file diff --git a/system/tts_engines/parler/model_engine.py b/system/tts_engines/parler/model_engine.py new file mode 100644 index 0000000..607b55d --- /dev/null +++ b/system/tts_engines/parler/model_engine.py @@ -0,0 +1,463 @@ +############################################### +# DONT CHANGE # These are base imports needed # +############################################### +import os +import sys +import json +import time +import torch +import logging +from pathlib import Path +from fastapi import (HTTPException) +logging.disable(logging.WARNING) +############################################### +# DONT CHANGE # Get Pytorch & Python versions # +############################################### +pytorch_version = torch.__version__ +cuda_version = torch.version.cuda +major, minor, micro = sys.version_info[:3] +python_version = f"{major}.{minor}.{micro}" +try: + import deepspeed + deepspeed_available = True +except ImportError: + deepspeed_available = False + pass + +############################################################################################################# +############################################################################################################# +# CHANGE ME # Run any specifc imports, requirements or setup any global vaiables needed for this TTS Engine # +############################################################################################################# +############################################################################################################# +# In this section you will import any imports that your specific TTS Engine will use. You will provide any +# start-up errors for those bits, as if you were starting up a normal Python script. Note the logging.disable +# a few lines up from here, you may want to # that out while debugging! +from parler_tts import ParlerTTSForConditionalGeneration +from transformers import AutoTokenizer +import soundfile as sf + +################################################################################################################################# +# DONT CHANGE # Do not change the Class name from tts_class as this is what will be imported into the main tts_server.py script # +################################################################################################################################# +class tts_class: + def __init__(self): + ######################################################################## + # DONT CHANGE # Sets up the base variables required for any tts engine # + ######################################################################## + self.branding = None + self.this_dir = Path(__file__).parent.resolve() # Sets up self.this_dir as a variable for the folder THIS script is running in. + self.main_dir = Path(__file__).parent.parent.parent.parent.resolve() # Sets up self.main_dir as a variable for the folder AllTalk is running in + self.device = "cuda" if torch.cuda.is_available() else "cpu" # Sets up self.device to cuda if torch exists with Nvidia/CUDA, otherwise sets to cpu + self.cuda_is_available = torch.cuda.is_available() # Sets up cuda_is_available as a True/False to track if Nvidia/CUDA was found on the system + self.tts_generating_lock = False # Used to lock and unlock the tts generation process at the start/end of tts generation. + self.tts_stop_generation = False # Used in conjunction with tts_generating_lock to call for a stop to the current generation. If called (set True) it needs to be set back to False when generation has been stopped. + self.tts_narrator_generatingtts = False # Used to track if the current tts processes is narrator based. This can be used in conjunction with lowvram and device to avoid moving model between GPU(CUDA)<>RAM(CPU) each chunk of narrated text generated. + self.model = None # If loading a model into CUDA/VRAM/RAM "model" is used as the variable name to load and interact with (see the XTTS model_engine script for examples.) + self.is_tts_model_loaded = False # Used to track if a model is actually loaded in and error/fail things like TTS generation if its False + self.current_model_loaded = None # Stores the name of the currenly loaded in model + self.available_models = None # List of available models found by "def scan_models_folder" + self.setup_has_run = False # Tracks if async def setup(self) has run, by setting to True, so that the /api/ready endpoint can provide a "Ready" status + ############################################################################################## + # DONT CHANGE # Load in a list of the available TTS engines and the currently set TTS engine # + ############################################################################################## + tts_engines_file = os.path.join(self.main_dir, "system", "tts_engines", "tts_engines.json") + with open(tts_engines_file, "r") as f: + tts_engines_data = json.load(f) + self.engines_available = [engine["name"] for engine in tts_engines_data["engines_available"]] # A list of ALL the TTS engines available to be loaded by AllTalk + self.engine_loaded = tts_engines_data["engine_loaded"] # In "tts_engines.json" what is the currently set TTS engine loading into AllTalk + self.selected_model = tts_engines_data["selected_model"] # In "tts_engines.json" what is the currently set TTS model loading into AllTalk + ############################################################################ + # DONT CHANGE # Pull out all the settings for the currently set TTS engine # + ############################################################################ + with open(os.path.join(self.this_dir, "model_settings.json"), "r") as f: + tts_model_loaded = json.load(f) + # Access the model details + self.manufacturer_name = tts_model_loaded["model_details"]["manufacturer_name"] # The company/person/body that generated the TTS engine/models etc + self.manufacturer_website = tts_model_loaded["model_details"]["manufacturer_website"] # The website of the company/person/body where people can find more information + # Access the features the model is capable of: + self.audio_format = tts_model_loaded["model_capabilties"]["audio_format"] # This details the audio format your TTS engine is set to generate TTS in e.g. wav, mp3, flac, opus, acc, pcm. Please use only 1x format. + self.deepspeed_capable = tts_model_loaded["model_capabilties"]["deepspeed_capable"] # Is your model capable of DeepSpeed + self.deepspeed_available = 'deepspeed' in globals() # When we did the import earlier, at the top of this script, was DeepSpeed available for use + self.generationspeed_capable = tts_model_loaded["model_capabilties"]["generationspeed_capable"] # Does this TTS engine support changing the speed of the generated TTS + self.languages_capable = tts_model_loaded["model_capabilties"]["languages_capable"] # Are the actual models themselves capable of generating in multiple languages OR is each model language specific + self.lowvram_capable = tts_model_loaded["model_capabilties"]["lowvram_capable"] # Is this engine capable of using low VRAM (moving the model between CPU And GPU memory) + self.multimodel_capable = tts_model_loaded["model_capabilties"]["multimodel_capable"] # Is there just the one model or are there multiple models this engine supports. + self.repetitionpenalty_capable = tts_model_loaded["model_capabilties"]["repetitionpenalty_capable"] # Is this TTS engine capable of changing the repititon penalty + self.streaming_capable = tts_model_loaded["model_capabilties"]["streaming_capable"] # Is this TTS engine capabale of generating streaming audio + self.temperature_capable = tts_model_loaded["model_capabilties"]["temperature_capable"] # Is this TTS engine capable of changing the temperature of the models + self.multivoice_capable = tts_model_loaded["model_capabilties"]["multivoice_capable"] # Are the models multi-voice or single vocice models + self.pitch_capable = tts_model_loaded["model_capabilties"]["pitch_capable"] # Is this TTS engine capable of changing the pitch of the genrated TTS + # Access the current enginesettings + self.def_character_voice = tts_model_loaded["settings"]["def_character_voice"] # What is the current default main/character voice that will be used if no voice specified. + self.def_narrator_voice = tts_model_loaded["settings"]["def_narrator_voice"] # What is the current default narrator voice that will be used if no voice specified. + self.deepspeed_enabled = tts_model_loaded["settings"]["deepspeed_enabled"] # If its available, is DeepSpeed enabled for the TTS engine + self.engine_installed = tts_model_loaded["settings"]["engine_installed"] # Has the TTS engine been setup/installed (not curently used) + self.generationspeed_set = tts_model_loaded["settings"]["generationspeed_set"] # What is the set/stored speed for generation. + self.lowvram_enabled = tts_model_loaded["settings"]["lowvram_enabled"] # If its available, is LowVRAM enabled for the TTS engine + # Check if someone has enabled lowvram on a system that's not CUDA enabled + self.lowvram_enabled = False if not torch.cuda.is_available() else self.lowvram_enabled # If LowVRAM is mistakenly set and CUDA is not available, this will force it back off + self.repetitionpenalty_set = tts_model_loaded["settings"]["repetitionpenalty_set"] # What is the currenly set repitition policy of the model (If it support repetition) + self.temperature_set = tts_model_loaded["settings"]["temperature_set"] # What is the currenly set temperature of the model (If it support temp) + self.pitch_set = tts_model_loaded["settings"]["pitch_set"] # What is the currenly set pitch of the model (If it support temp) + # Gather the OpenAI API Voice Mappings + self.openai_alloy = tts_model_loaded["openai_voices"]["alloy"] # The TTS engine voice that will be mapped to Open AI Alloy voice + self.openai_echo = tts_model_loaded["openai_voices"]["echo"] # The TTS engine voice that will be mapped to Open AI Echo voice + self.openai_fable = tts_model_loaded["openai_voices"]["fable"] # The TTS engine voice that will be mapped to Open AI Fable voice + self.openai_nova = tts_model_loaded["openai_voices"]["nova"] # The TTS engine voice that will be mapped to Open AI Nova voice + self.openai_onyx = tts_model_loaded["openai_voices"]["onyx"] # The TTS engine voice that will be mapped to Open AI Onyx voice + self.openai_shimmer = tts_model_loaded["openai_voices"]["shimmer"] # The TTS engine voice that will be mapped to Open AI Shimmer voice + ################################################################### + # DONT CHANGE # Load params and api_defaults from confignew.json # + ################################################################### + # Define the path to the confignew.json file + configfile_path = self.main_dir / "confignew.json" + # Load config file and get settings + with open(configfile_path, "r") as configfile: + configfile_data = json.load(configfile) + self.branding = configfile_data.get("branding", "") # Sets up self.branding for outputting the name stored in the "confgnew.json" file, as used in print statements. + self.params = configfile_data # Loads in the curent "confgnew.json" file to self.params. + self.debug_tts = configfile_data.get("debugging").get("debug_tts") # Can be used within this script as a True/False flag for generally debugging the TTS generation process. + self.debug_tts_variables = configfile_data.get("debugging").get("debug_tts_variables") # Can be used within this script as a True/False flag for generally debugging variables (if you wish). + ################################################################ + # DONT CHANGE # Print out Python, CUDA, DeepSpeed versions #### + ################################################################ + def printout_versions(self): + if deepspeed_available: + print(f"[{self.branding}ENG] \033[92mDeepSpeed version :\033[93m",deepspeed.__version__,"\033[0m") + else: + print(f"[{self.branding}ENG] \033[92mDeepSpeed version :\033[93m Not available\033[0m") + print(f"[{self.branding}ENG] \033[92mPython Version :\033[93m {python_version}\033[0m") + print(f"[{self.branding}ENG] \033[92mPyTorch Version :\033[93m {pytorch_version}\033[0m") + if cuda_version is None: + print(f"[{self.branding}ENG] \033[92mCUDA Version :\033[91m Not available\033[0m") + else: + print(f"[{self.branding}ENG] \033[92mCUDA Version :\033[93m {cuda_version}\033[0m") + print(f"[{self.branding}ENG]") + return + + ################################################################################### + ################################################################################### + # CHANGE ME # Inital setup of the model and engine. Called when the script starts # + ################################################################################### + ################################################################################### + # In here you will add code to load in your model and do its inital setup. So you + # may be calling your model loader via handle_tts_method_change or if your TTS + # engine doesnt actually load a model into CUDA or System RAM, you may be doing + # Something to fake its start-up. + async def setup(self): + self.printout_versions() + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + self.available_models = self.scan_models_folder() + if self.selected_model: + tts_model = f"{self.selected_model}" + if tts_model in self.available_models: + await self.handle_tts_method_change(tts_model) + self.current_model_loaded = tts_model + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + else: + self.current_model_loaded = "No Models Available" + print(f"[{self.branding}ENG] \033[91mError\033[0m: Selected model '{self.selected_model}' not found in the models folder.") + self.setup_has_run = True # Set to True, so that the /api/ready endpoint can provide a "Ready" status + + ################################## + ################################## + # CHANGE ME # Low VRAM Swapping # + ################################## + ################################## + # If your model does load into CUDA and you want to support LowVRAM, aka moving the model + # on the fly between CUDA and System RAM on each generation request, you will be adding + # The code in here to do it. See the XTTS tts engine for an example. Piper however, doesnt + # Load into VRAM or System RAM, also low vram is set globally disabled by the model_settings.JSON + # file, so this would never get called anywyay, however, we still need to keep an empty + # function in place. Piper TTS uses "pass" telling function to just exit out cleanly if called. + # However, its quite a simple check along the lines of "if CUDA is available and model is + # in X place, then send it to Y place (or Y to X). + async def handle_lowvram_change(self): + if torch.cuda.is_available(): + if self.device == "cuda": + self.device = "cpu" + self.model.to(self.device) + torch.cuda.empty_cache() + else: + self.device == "cpu" + self.device = "cuda" + self.model.to(self.device) + + ######################################## + ######################################## + # CHANGE ME # DeepSpeed model loading # + ######################################## + ######################################## + # If the model supports CUDA and DeepSpeed, this is where you can handle re-loading + # the model as/when DeepSpeed is enabled/selected in the user interface. If your + # TTS model doesnt support DeepSpeed, then it should be globally set in your + # model_settings.JSON and this function will never get called, however it still needs + # to exist as a function. + async def handle_deepspeed_change(self, value): + if value: + # DeepSpeed enabled + print(f"[{self.branding}ENG] \033[93mDeepSpeed Activating\033[0m") + await self.unload_model() + self.params["tts_method_api_local"] = False + self.params["tts_method_xtts_local"] = True + self.deepspeed_enabled = True + await self.setup() + else: + # DeepSpeed disabled + print(f"[{self.branding}ENG] \033[93mDeepSpeed De-Activating\033[0m") + self.deepspeed_enabled = False + await self.unload_model() + await self.setup() + return value # Return new checkbox value + + ############################################################################################################################################## + ############################################################################################################################################## + # CHANGE ME # scan for available models/voices that are relevant to this TTS engine # XTTS is very unusal as it has 2x model loading methods # + ############################################################################################################################################## + ############################################################################################################################################## + # This function looks and reports back the list of possible models your TTS engine can + # load in. Some TTS engines have multiple models they can load and you will want to use + # code for checking if the models are in the correct location/placement within the disk + # the correct files exst per model etc (check XTTS for an example of this). Some models + # like Piper, the actual models are the voices, so in the Piper scan_models_folder + # function, we fake the only model being available as {'piper': 'piper'} aka, model name + # then engine name, then we use the voices_file_list to populate the models as available + # voices that can be selected in the interface. + # If no models are found, we return "No Models Available" and continue on with the script. + def scan_models_folder(self): + models_folder = self.main_dir / "models" / "parler" # Edit to match the name of your folder where voices are stored + print("models_folder is:", models_folder) if self.debug_tts else None + self.available_models = {} + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # Check if model_path exists + if not os.path.exists(models_folder): + print(f"[{self.branding}ENG] \033[91mError\033[0m: The specified model path '{models_folder}' does not exist.") + self.available_models = {'No Models Available': 'parler'} # Change the name in here to your TTS engine name + return self.available_models + required_files = ["config.json", "generation_config.json", "model.safetensors", "preprocessor_config.json", "special_tokens_map.json", "spiece.model", "tokenizer.json", "tokenizer_config.json"] + for subfolder in models_folder.iterdir(): + if subfolder.is_dir(): + model_name = subfolder.name + print("model_name is:", model_name) if self.debug_tts else None + if all(subfolder.joinpath(file).exists() for file in required_files): + self.available_models[f"parler - {model_name}"] = "parler" + print("self.available_models is:", self.available_models) if self.debug_tts else None + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + else: + self.available_models = {'No Models Available': 'parler'} # Change the name in here to your TTS engine name + print(f"[{self.branding}ENG] \033[91mWarning\033[0m: Model folder '{model_name}' is missing required") + print(f"[{self.branding}ENG] \033[91mWarning\033[0m: files or the folder does not exist.") + return self.available_models + + ############################################################# + ############################################################# + # CHANGE ME # POPULATE FILES LIST FROM VOICES DIRECTORY #### + ############################################################# + ############################################################# + # This function looks and reports back the list of possible voice your TTS engine can + # load in. Some TTS engines the voices are wav file samples (XTTS), some are models + # (Piper) and some are text (Parler) thats stored in a JSON file. We just need to + # populate the "voices" variable somehow and if no voices are found, we return + # "No Voices Found" back to the interface/api. + def voices_file_list(self): + try: + voices = [] + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + voices_file = os.path.join(self.main_dir, "system", "tts_engines", "parler", "parler_voices.json") + if os.path.exists(voices_file): + with open(voices_file, "r") as f: + voices = json.load(f) + return [voice["voice_name"] for voice in voices["voices"]] + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + if not voices: + return ["No Voices Found"] # Return a list with {'No Voices Found'} if there are no voices/voice models. + return voices # Return a list of models in the format {'engine name': 'model 1', 'engine name': 'model 2", etc} + except Exception as e: + print(f"[{self.branding}ENG] \033[91mError\033[0m: Voices/Voice Models not found. Cannot load a list of voices.") + print(f"[{self.branding}ENG]") + return ["No Voices Found"] + + ############################# + ############################# + # CHANGE ME # Model loading # + ############################# + ############################# + # This function will handle the loading of your model, into VRAM/CUDA, System RAM or whatever. + # In XTTS, which has 2x model loader types, there are 2x loaders. They are called by "def handle_tts_method_change" + # In Piper we fake a model loader as Piper doesnt actually load a model into CUDA/System RAM as such. So, in that + # situation, api_manual_load_model is kind of a blank function. Though we do set self.is_tts_model_loaded = True + # as this is used elsewhere in the scripts to confirm that a model is available to be used for TTS generation. + # We always check for "No Models Available" being sent as that means we are trying to load in a model that + # doesnt exist/wasnt found on script start-up e.g. someone deleted the model from the folder or something. + async def api_manual_load_model(self, model_name): + if "No Models Available" in self.available_models: + print(f"[{self.branding}ENG] \033[91mError\033[0m: No models for this TTS engine were found to load. Please download a model.") + return + model_path = self.main_dir / "models" / "parler" / model_name + print("model_path is:", model_path) if self.debug_tts_variables else None + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + self.model = ParlerTTSForConditionalGeneration.from_pretrained(model_path).to(self.device, dtype=torch.float32) + self.tokenizer = AutoTokenizer.from_pretrained(model_path) + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + self.is_tts_model_loaded = True + return self.model + + ############################### + ############################### + # CHANGE ME # Model unloading # + ############################### + ############################### + # This function will handle the UN-loading of your model, from VRAM/CUDA, System RAM or whatever. + # In XTTS, that model loads into CUDA/System Ram, so when we swap models, we want to unload the current model + # free up the memory and then load in the new model to VRAM/CUDA. On the flip side of that, Piper doesnt + # doesnt load into memory, so we just need to put a fake function here that doesnt really do anything + # other than set "self.is_tts_model_loaded = False", which would be set back to true by the model loader. + # So look at the Piper model_engine.py if you DONT need to unload models. + async def unload_model(self): + self.is_tts_model_loaded = False + if not self.current_model_loaded == None: + print(f"[{self.branding}ENG] \033[94mUnloading model \033[0m") if self.debug_tts else None + if hasattr(self, 'model'): + del self.model + if torch.cuda.is_available(): + torch.cuda.empty_cache() + return None + + ################################################################################################################################### + ################################################################################################################################### + # CHANGE ME # Model changing. Unload out old model and load in a new one # XTTS is very unusal as it has 2x model loading methods # + ################################################################################################################################### + ################################################################################################################################### + # This function is your central model loading/unloading handler that deals with the above functions as necesary, to call loading, unloading, + # swappng DeepSpeed, Low vram etc. This function gets called with a "engine name - model name" type call. In XTTS, because there are 2x + # model loader types, (XTTS and APILocal), we take tts_method and split the "engine name - model name" into a loader type and the model + # that it needs to load in and then we call the correct loader function. Whereas in Piper, which doesnt load models into memory at all, + # we just have a fake function that doesnt really do anything. We always check to see if the model name has "No Models Available" in the + # name thats sent over, just to catch any potential errors. We display the start load time and end load time. Thats about it. + async def handle_tts_method_change(self, tts_method): + generate_start_time = time.time() # Record the start time of loading the model + if "No Models Available" in self.available_models: + print(f"[{self.branding}ENG] \033[91mError\033[0m: No models for this TTS engine were found to load. Please download a model.") + return False + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + await self.unload_model() + if tts_method.startswith("parler"): + model_name = tts_method.split(" - ")[1] + print(f"[{self.branding}ENG]\033[94m Model/Engine :\033[93m {model_name}\033[94m loading into\033[93m", self.device,"\033[0m") + self.model = await self.api_manual_load_model(model_name) + self.current_model_loaded = f"parler - {model_name}" + else: + self.current_model_loaded = None + return False + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + generate_end_time = time.time() # Create an end timer for calculating load times + generate_elapsed_time = generate_end_time - generate_start_time # Calculate start time minus end time + print(f"[{self.branding}ENG] \033[94mLoad time :\033[93m {generate_elapsed_time:.2f} seconds.\033[0m") # Print out the result of the load time + return True + + ########################################################################################################################################## + ########################################################################################################################################## + # CHANGE ME # Model changing. Unload out old model and load in a new one # XTTS is very unusal as it has 2x model TTS generation methods # + ########################################################################################################################################## + ########################################################################################################################################## + # In here all the possible options are sent over (text, voice to use, lanugage, speed etc etc) and its up to you how you use them, or not. + # obviously if your TTS engine doesnt support speed for example, generationspeed_capable should be set False in your model_settings.JSON file + # and a fake "generationspeed_set" value should be set. This allows a fake value to be sent over from the main script, even though it + # wouldnt actually ever be used in the generation below. Nonethless all these values, real or just whats inside the configuration file + # will be sent over for use. + # Setting the xxxxxxx_capabale in the model_settings.JSON file, will enable/disable them being selectable by the user. For example, if you + # set "generationspeed_capable" as false in the model_settings.JSON file, a user will not be able to select OR set the setting for + # generation speed. + # One thing to note is that we HAVE to have something in this generation request that is synchronous from the way its called, which means + # we have to have an option for Streaming, even if our TTS engine doesnt support streaming. So in that case, we would set streaming_capable + # as false in our model_settings.JSON file, meaning streaming will never be called. However, we have to put a fake streaming routine in our + # function below (or a real function if it does support streaming of course). Parler has an example of a fake streaming function, which is + # very clearly highlighted in its model_engine.py script. + # Piper TTS, which uses command line based calls and therefore has different ones for Windows and Linux/Mac, has an example of doing this + # within its model_engine.py file. + async def generate_tts(self, text, voice, language, temperature, repetition_penalty, speed, pitch, output_file, streaming): + print(f"[{self.branding}Debug] Entered model_engine.py generate_tts function") if self.debug_tts else None + if not self.is_tts_model_loaded: # Check if a model is loaded and error out if not. + error_message = f"[{self.branding}ENG] \033[91mError\033[0m: You currently have no TTS model loaded." + print(error_message) + raise HTTPException(status_code=400, detail="You currently have no TTS model loaded.") # Raise an exception with a meaningful HTTP status code + self.tts_generating_lock = True # Set the tts_generating lock to True, which stops other generation requests being sent into the pipeline + print(f"[{self.branding}Debug] Checking low VRAM") if self.debug_tts else None + if self.lowvram_enabled and self.device == "cpu": # If necessary, move the model out of System Ram to VRAM + print(f"[{self.branding}Debug] Switching device") if self.debug_tts else None + await self.handle_lowvram_change() + print(f"[{self.branding}Debug] Setting a generate time") if self.debug_tts else None + generate_start_time = time.time() # Record the start time of generating TTS + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + voices_file = os.path.join(self.main_dir, "system", "tts_engines", "parler", "parler_voices.json") + if os.path.exists(voices_file): + with open(voices_file, "r") as f: + voices_data = json.load(f) + for voice_data in voices_data["voices"]: + if voice_data["voice_name"] == voice: + description = voice_data["description"] + break + else: + description = f"A {voice} voice speaking in {language}." + else: + description = f"A {voice} voice speaking in {language}." + input_ids = self.tokenizer(description, return_tensors="pt").input_ids.to(self.device) + prompt_input_ids = self.tokenizer(text, return_tensors="pt").input_ids.to(self.device) + generation = self.model.generate(input_ids=input_ids, prompt_input_ids=prompt_input_ids).to(torch.float32) + audio_arr = generation.cpu().numpy().squeeze() + sf.write(output_file, audio_arr, self.model.config.sampling_rate) + + # Fake Streaming function here + wavs = None # This is a fake function as this model doesnt stream but Python demands it otherwise it complains about await functions in the main script + if streaming and wavs is not None: # This is a fake function as this model doesnt stream but Python demands it otherwise it complains about await functions in the main script + # Streaming-specific operations # This is a fake function as this model doesnt stream but Python demands it otherwise it complains about await functions in the main script + for wav_chunk in wavs: # This is a fake function as this model doesnt stream but Python demands it otherwise it complains about await functions in the main script + yield wav_chunk.numpy().tobytes() # This is a fake function as this model doesnt stream but Python demands it otherwise it complains about await functions in the main script + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + generate_end_time = time.time() # Record the end time to generate TTS + generate_elapsed_time = generate_end_time - generate_start_time + print(f"[{self.branding}GEN] \033[94mTTS Generate: \033[93m{generate_elapsed_time:.2f} seconds. \033[94mLowVRAM: \033[33m{self.lowvram_enabled} \033[94mDeepSpeed: \033[33m{self.deepspeed_enabled}\033[0m") + if self.lowvram_enabled and self.device == "cuda" and self.tts_narrator_generatingtts == False: + await self.handle_lowvram_change() + self.tts_generating_lock = False # Unlock the TTS generation queue to allow TTS generation requests to come in again. + print(f"[{self.branding}Debug] generate_tts function completed") if self.debug_tts else None + return + + + + diff --git a/system/tts_engines/parler/model_settings.json b/system/tts_engines/parler/model_settings.json new file mode 100644 index 0000000..9bc3a38 --- /dev/null +++ b/system/tts_engines/parler/model_settings.json @@ -0,0 +1,42 @@ +{ + "model_details": { + "manufacturer_name": "Parler-TTS", + "manufacturer_website": "https://github.com/huggingface/parler-tts", + "model_description": "Parler-TTS is a lightweight text-to-speech (TTS) model that can generate high-quality, natural sounding speech in the style of a given speaker (gender, pitch, speaking style, etc). It is a reproduction of work from the paper Natural language guidance of high-fidelity text-to-speech with synthetic annotations by Dan Lyth and Simon King, from Stability AI and Edinburgh University respectively. Contrarily to other TTS models, Parler-TTS is a fully open-source release. All of the datasets, pre-processing, training code and weights are released publicly under permissive license, enabling the community to build on our work and develop their own powerful TTS models. \n\nParler's unique party trick is that like Stable Diffusion, you can describe the voice you want in a text entry; however, this means you are unlikely to get the same voice reproduced each time. Please see the Voice Editor tab." + }, + "model_capabilties": { + "audio_format": "wav", + "deepspeed_capable": false, + "generationspeed_capable": true, + "languages_capable": false, + "lowvram_capable": true, + "multimodel_capable": true, + "repetitionpenalty_capable": false, + "streaming_capable": false, + "temperature_capable": false, + "multivoice_capable": true, + "pitch_capable": false, + "windows_capable": true, + "linux_capable": true, + "mac_capable": true + }, + "settings": { + "def_character_voice": "enthusiastic_female", + "def_narrator_voice": "enthusiastic_female", + "deepspeed_enabled": false, + "engine_installed": true, + "generationspeed_set": 1, + "lowvram_enabled": true, + "pitch_set": 0, + "repetitionpenalty_set": 10, + "temperature_set": 0.75 + }, + "openai_voices": { + "alloy": "enthusiastic_female", + "echo": "enthusiastic_female", + "fable": "enthusiastic_female", + "nova": "enthusiastic_female", + "onyx": "enthusiastic_female", + "shimmer": "enthusiastic_female" + } +} \ No newline at end of file diff --git a/system/tts_engines/parler/parler_settings_page.py b/system/tts_engines/parler/parler_settings_page.py new file mode 100644 index 0000000..5551281 --- /dev/null +++ b/system/tts_engines/parler/parler_settings_page.py @@ -0,0 +1,402 @@ +import os +import json +import requests +import gradio as gr +from tqdm import tqdm +from pathlib import Path +this_dir = Path(__file__).parent.resolve() # Sets up self.this_dir as a variable for the folder THIS script is running in. +main_dir = Path(__file__).parent.parent.parent.parent.resolve() # Sets up self.main_dir as a variable for the folder AllTalk is running in + +########################################################################## +# REQUIRED CHANGE # +# Populate the voices list, using the method specific to your TTS engine # +########################################################################## +# This function is responsible for populating the list of available voices for your TTS engine. +# You need to modify this function to use the appropriate method for your engine to retrieve the voice list. +# +# The current implementation lists all the WAV files in a "voices" directory, which may not be suitable for your engine. +# You should replace the `xxxx_voices_file_list` function name to match your engine name. For example, if your engine +# is named "mytts", the function should be named `mytts_voices_file_list`. +# +# You will also neef to update the code with your own implementation that retrieves the voice list according to your +# engine's specific requirements. Typically this is the same code as will be in your model_engine.py file. +# +# For example, if your engine has a dedicated API or configuration file for managing voices, you should modify this +# function to interact with that API or read from that configuration file. +# +# After making the necessary changes, this function should return a list of available voices that can be used +# in your TTS engine's settings page. +def parler_voices_file_list(): + voices_file = os.path.join(main_dir, "system", "tts_engines", "parler", "parler_voices.json") + if os.path.exists(voices_file): + with open(voices_file, "r") as f: + voices_data = json.load(f) + voices_list = [voice["voice_name"] for voice in voices_data["voices"]] + return sorted(voices_list) + else: + return [] + + +###################################################### +# REQUIRED CHANGE # +# Imports and saves the TTS engine-specific settings # +###################################################### +# This function is responsible for importing and saving the settings specific to your TTS engine. +# You need to make the following change: +# +# 1. Change the name of the function `xxxx_model_update_settings` to match your engine's name. +# For example, if your engine is named "mytts", the function should be named `mytts_model_update_settings`. +# +# After making this change, the function will load the model settings from a JSON file, update the settings and voice +# dictionaries with the values provided as arguments, and save the updated settings back to the JSON file. +# +# You do not need to modify the function's logic or any other part of the code. +def parler_model_update_settings(def_character_voice_gr, def_narrator_voice_gr, lowvram_enabled_gr, deepspeed_enabled_gr, temperature_set_gr, repetitionpenalty_set_gr, pitch_set_gr, generationspeed_set_gr, alloy_gr, echo_gr, fable_gr, nova_gr, onyx_gr, shimmer_gr): + # Load the model_config_data from the JSON file + with open(os.path.join(this_dir, "model_settings.json"), "r") as f: + model_config_data = json.load(f) + # Update the settings and openai_voices dictionaries with the new values + model_config_data["settings"]["def_character_voice"] = def_character_voice_gr + model_config_data["settings"]["def_narrator_voice"] = def_narrator_voice_gr + model_config_data["openai_voices"]["alloy"] = alloy_gr + model_config_data["openai_voices"]["echo"] = echo_gr + model_config_data["openai_voices"]["fable"] = fable_gr + model_config_data["openai_voices"]["nova"] = nova_gr + model_config_data["openai_voices"]["onyx"] = onyx_gr + model_config_data["openai_voices"]["shimmer"] = shimmer_gr + model_config_data["settings"]["lowvram_enabled"] = lowvram_enabled_gr == "Enabled" + model_config_data["settings"]["deepspeed_enabled"] = deepspeed_enabled_gr == "Enabled" + model_config_data["settings"]["temperature_set"] = temperature_set_gr + model_config_data["settings"]["repetitionpenalty_set"] = repetitionpenalty_set_gr + model_config_data["settings"]["pitch_set"] = pitch_set_gr + model_config_data["settings"]["generationspeed_set"] = generationspeed_set_gr + # Save the updated model_config_data to the JSON file + with open(os.path.join(this_dir, "model_settings.json"), "w") as f: + json.dump(model_config_data, f, indent=4) + return "Settings updated successfully!" + +####################################################### +# REQUIRED CHANGE # +# Sets up the engine-specific settings page in Gradio # +####################################################### +# This function sets up the Gradio interface for the settings page specific to your TTS engine. +# You need to make the following changes: +# +# 1. Change the name of the function `xxxx_model_alltalk_settings` to match your engine's name. +# For example, if your engine is named "mytts", the function should be named `mytts_model_alltalk_settings`. +# +# 2. Change the name of the `submit_button.click` function call to match the name you gave to the function +# that imports and saves your engine's settings (the function you modified above). +# +# 3. Change the name of the `voice_list` function call to match the name of the function that lists +# the available voices for your TTS engine. +# +# 4. Change the 'title' of the `gr.Blocks` to match your engine's name e.g. title="mytts TTS" +# +# After making these changes, this function will create and return the Gradio interface for your TTS engine's +# settings page, allowing users to configure various options and voice selections. +def parler_model_alltalk_settings(model_config_data): + features_list = model_config_data['model_capabilties'] + voice_list = parler_voices_file_list() + with gr.Blocks(title="parler TTS") as app: + with gr.Tab("Default Settings"): + with gr.Row(): + lowvram_enabled_gr = gr.Radio(choices={"Enabled": "true", "Disabled": "false"}, label="Low VRAM" if model_config_data["model_capabilties"]["lowvram_capable"] else "Low VRAM N/A", value="Enabled" if model_config_data["settings"]["lowvram_enabled"] else "Disabled", interactive=model_config_data["model_capabilties"]["lowvram_capable"]) + deepspeed_enabled_gr = gr.Radio(choices={"Enabled": "true", "Disabled": "false"}, label="DeepSpeed Activate" if model_config_data["model_capabilties"]["deepspeed_capable"] else "DeepSpeed N/A", value="Enabled" if model_config_data["settings"]["deepspeed_enabled"] else "Disabled", interactive=model_config_data["model_capabilties"]["deepspeed_capable"]) + temperature_set_gr = gr.Slider(value=float(model_config_data["settings"]["temperature_set"]), minimum=0, maximum=1, step=0.05, label="Temperature" if model_config_data["model_capabilties"]["temperature_capable"] else "Temperature N/A", interactive=model_config_data["model_capabilties"]["temperature_capable"]) + repetitionpenalty_set_gr = gr.Slider(value=float(model_config_data["settings"]["repetitionpenalty_set"]), minimum=1, maximum=20, step=1, label="Repetition Penalty" if model_config_data["model_capabilties"]["repetitionpenalty_capable"] else "Repetition N/A", interactive=model_config_data["model_capabilties"]["repetitionpenalty_capable"]) + pitch_set_gr = gr.Slider(value=float(model_config_data["settings"]["pitch_set"]), minimum=-10, maximum=10, step=1, label="Pitch" if model_config_data["model_capabilties"]["pitch_capable"] else "Pitch N/A", interactive=model_config_data["model_capabilties"]["pitch_capable"]) + generationspeed_set_gr = gr.Slider(value=float(model_config_data["settings"]["generationspeed_set"]), minimum=0.25, maximum=2.00, step=0.25, label="Speed" if model_config_data["model_capabilties"]["generationspeed_capable"] else "Speed N/A", interactive=model_config_data["model_capabilties"]["generationspeed_capable"]) + with gr.Row(): + with gr.Column(): + gr.Markdown("### OpenAI Voice Mappings") + with gr.Group(): + with gr.Row(): + alloy_gr = gr.Dropdown(value=model_config_data["openai_voices"]["alloy"], label="Alloy", choices=voice_list, allow_custom_value=True) + echo_gr = gr.Dropdown(value=model_config_data["openai_voices"]["echo"], label="Echo", choices=voice_list, allow_custom_value=True) + with gr.Row(): + fable_gr = gr.Dropdown(value=model_config_data["openai_voices"]["fable"], label="Fable", choices=voice_list, allow_custom_value=True) + nova_gr = gr.Dropdown(value=model_config_data["openai_voices"]["nova"], label="Nova", choices=voice_list, allow_custom_value=True) + with gr.Row(): + onyx_gr = gr.Dropdown(value=model_config_data["openai_voices"]["onyx"], label="Onyx", choices=voice_list, allow_custom_value=True) + shimmer_gr = gr.Dropdown(value=model_config_data["openai_voices"]["shimmer"], label="Shimmer", choices=voice_list, allow_custom_value=True) + with gr.Column(): + gr.Markdown("### Default Voices") + with gr.Row(): + def_character_voice_gr = gr.Dropdown(value=model_config_data["settings"]["def_character_voice"], label="Default/Character Voice", choices=voice_list, allow_custom_value=True) + def_narrator_voice_gr = gr.Dropdown(value=model_config_data["settings"]["def_narrator_voice"], label="Narrator Voice", choices=voice_list, allow_custom_value=True) + with gr.Group(): + with gr.Row(): + details_text = gr.Textbox(label="Details", show_label=False, lines=5, interactive=False, value="In this section, you can set the default settings for this TTS engine. Settings that are not supported by the current engine will be greyed out and cannot be selected. Default voices specified here will be used when no specific voice is provided in the TTS generation request. If a voice is specified in the request, it will override these default settings. When using the OpenAI API compatable API with this TTS engine, the voice mappings will be applied. As the OpenAI API has a limited set of 6 voices, these mappings ensure compatibility by mapping the OpenAI voices to the available voices in this TTS engine.") + with gr.Row(): + submit_button = gr.Button("Update Settings") + output_message = gr.Textbox(label="Output Message", interactive=False, show_label=False) + submit_button.click(parler_model_update_settings, inputs=[def_character_voice_gr, def_narrator_voice_gr, lowvram_enabled_gr, deepspeed_enabled_gr, temperature_set_gr, repetitionpenalty_set_gr, pitch_set_gr, generationspeed_set_gr, alloy_gr, echo_gr, fable_gr, nova_gr, onyx_gr, shimmer_gr], outputs=output_message) + + ########################################################################################### + # Do not change this section apart from "TTS Engine Name" value to match your engine name # + ########################################################################################### + with gr.Tab("Engine Information"): + with gr.Row(): + with gr.Group(): + gr.Textbox(label="Manufacturer Name", value=model_config_data['model_details']['manufacturer_name'], interactive=False) + gr.Textbox(label="Manufacturer Website/TTS Engine Support", value=model_config_data['model_details']['manufacturer_website'], interactive=False) + gr.Textbox(label="Engine/Model Description", value=model_config_data['model_details']['model_description'], interactive=False, lines=13) + with gr.Column(): + with gr.Row(): + gr.Textbox(label="DeepSpeed Capable", value='Yes' if features_list['deepspeed_capable'] else 'No', interactive=False) + gr.Textbox(label="Pitch Capable", value='Yes' if features_list['pitch_capable'] else 'No', interactive=False) + gr.Textbox(label="Generation Speed Capable", value='Yes' if features_list['generationspeed_capable'] else 'No', interactive=False) + with gr.Row(): + gr.Textbox(label="Repetition Penalty Capable", value='Yes' if features_list['repetitionpenalty_capable'] else 'No', interactive=False) + gr.Textbox(label="Multi Languages Capable", value='Yes' if features_list['languages_capable'] else 'No', interactive=False) + gr.Textbox(label="Streaming Capable", value='Yes' if features_list['streaming_capable'] else 'No', interactive=False) + with gr.Row(): + gr.Textbox(label="Low VRAM Capable", value='Yes' if features_list['lowvram_capable'] else 'No', interactive=False) + gr.Textbox(label="Temperature Capable", value='Yes' if features_list['temperature_capable'] else 'No', interactive=False) + gr.Textbox(label="Multi Model Capable Engine", value='Yes' if features_list['multimodel_capable'] else 'No', interactive=False) + with gr.Row(): + gr.Textbox(label="Multi Voice Capable Models", value='Yes' if features_list['multivoice_capable'] else 'No', interactive=False) + gr.Textbox(label="Default Audio output format", value=model_config_data['model_capabilties']['audio_format'], interactive=False) + gr.Textbox(label="TTS Engine Name", value="parler", interactive=False) + with gr.Row(): + gr.Textbox(label="Windows Support", value='Yes' if features_list['windows_capable'] else 'No', interactive=False) + gr.Textbox(label="Linux Support", value='Yes' if features_list['linux_capable'] else 'No', interactive=False) + gr.Textbox(label="Mac Support", value='Yes' if features_list['mac_capable'] else 'No', interactive=False) + with gr.Row(): + gr.Markdown(""" + #### 🟧 DeepSpeed Capable + DeepSpeed is a deep learning optimization library that can significantly speed up model training and inference. If a model is DeepSpeed capable, it means it can utilize DeepSpeed to accelerate the generation of text-to-speech output. This requires the model to be loaded into CUDA/VRAM on an Nvidia GPU and the model's inference method to support DeepSpeed. + #### 🟧 Pitch Capable + Pitch refers to the highness or lowness of a sound. If a model is pitch capable, it can adjust the pitch of the generated speech, allowing for more expressive and varied output. + #### 🟧 Generation Speed Capable + Generation speed refers to the rate at which the model can generate text-to-speech output. If a model is generation speed capable, it means the speed of the generated speech can be adjusted, making it faster or slower depending on the desired output. + #### 🟧 Repetition Penalty Capable + Repetition penalty is a technique used to discourage the model from repeating the same words or phrases multiple times in the same sounding way. If a model is repetition penalty capable, it can apply this penalty during generation to improve the diversity and naturalness of the output. + #### 🟧 Multi-Languages Capable + Multi-language capable models can generate speech in multiple languages. This means that the model has been trained on data from different languages and can switch between them during generation. Some models are language-specific. + #### 🟧 Multi-Voice Capable + Multi-voice capable models generate speech in multiple voices or speaking styles. This means that the model has been trained on data from different speakers and can mimic their voices during generation, or is a voice cloning model that can generate speech based on the input sample. + """) + gr.Markdown(""" + #### 🟧 Streaming Capable + Streaming refers to the ability to generate speech output in real-time, without the need to generate the entire output before playback. If a model is streaming capable, it can generate speech on-the-fly, allowing for faster response times and more interactive applications. + #### 🟧 Low VRAM Capable + VRAM (Video Random Access Memory) is a type of memory used by GPUs to store and process data. If a model is low VRAM capable, it can efficiently utilize the available VRAM by moving data between CPU and GPU memory as needed, allowing for generation even on systems with limited VRAM where it may be competing with an LLM model for VRAM. + #### 🟧 Temperature Capable + Temperature is a hyperparameter that controls the randomness of the generated output. If a model is temperature capable, the temperature can be adjusted to make the output more or less random, affecting the creativity and variability of the generated speech. + #### 🟧 Multi-Model Capable Engine + If an engine is multi-model capable, it means that it can support and utilize multiple models for text-to-speech generation. This allows for greater flexibility and the ability to switch between different models depending on the desired output. Different models may be capable of different languages, specific languages, voices, etc. + #### 🟧 Default Audio Output Format + Specifies the file format in which the generated speech will be saved. Common audio formats include WAV, MP3, FLAC, Opus, AAC, and PCM. If you want different outputs, you can set the transcode function to change the output audio, though transcoding will add a little time to the generation and is not available for streaming generation. + #### 🟧 Windows/Linux/Mac Support + These indicators show whether the model and engine are compatible with Windows, Linux, or macOS. However, additional setup or requirements may be necessary to ensure full functionality on your operating system. Please note that full platform support has not been extensively tested. + """) + + ####################################################################################################################################################################################################### + # REQUIRED CHANGE # + # You will need to build a custom method to identify if your models are installed and download them. Store your models list in the available_models.json file, alongside your model_settings.json etc # + ####################################################################################################################################################################################################### + with gr.Tab("Models/Voices Download"): + with gr.Row(): + # Load the available models from the JSON file + with open(os.path.join(this_dir, "available_models.json"), "r") as f: + available_models = json.load(f) + # Extract the model names for the dropdown + model_names = [model["model_name"] for model in available_models["models"]] + # Create the dropdown + model_dropdown = gr.Dropdown(choices=sorted(model_names), label="Select Model", value=model_names[0]) + download_button = gr.Button("Download Model/Missing Files") + + def download_model(model_name, force_download=False): + # Find the selected model in the available models + selected_model = next(model for model in available_models["models"] if model["model_name"] == model_name) + # Get the folder path and files to download + folder_path = os.path.join(main_dir, "models", "parler", model_name) + files_to_download = selected_model["files_to_download"] + # Check if all files are already downloaded + all_files_exists = all(os.path.exists(os.path.join(folder_path, os.path.basename(url.split('?')[0]))) for url in files_to_download) + if all_files_exists and not force_download: + return "All files are already downloaded. No need to download again." + else: + # Create the folder if it doesn't exist + os.makedirs(folder_path, exist_ok=True) + # Download the missing files + for url in files_to_download: + file_name = os.path.basename(url.split('?')[0]) + file_path = os.path.join(folder_path, file_name) + if not os.path.exists(file_path) or force_download: + print(f"Downloading {file_name}...") + response = requests.get(url, stream=True) + total_size_in_bytes = int(response.headers.get("content-length", 0)) + block_size = 1024 # 1 Kibibyte + progress_bar = tqdm(total=total_size_in_bytes, unit="iB", unit_scale=True) + with open(file_path, "wb") as file: + for data in response.iter_content(block_size): + progress_bar.update(len(data)) + file.write(data) + progress_bar.close() + return "Model downloaded successfully!" + + with gr.Row(): + download_status = gr.Textbox(label="Download Status") + + download_button.click(download_model, inputs=model_dropdown, outputs=download_status) + + def show_confirm_cancel(model_name): + all_files_exists = all(os.path.exists(os.path.join(main_dir, "models", "parler", os.path.basename(file.split('?')[0]))) for model in available_models["models"] if model["model_name"] == model_name for file in model["files_to_download"]) + if all_files_exists: + return [gr.update(visible=True), gr.update(visible=True), gr.update(visible=True)] + else: + return [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)] + + def confirm_download(model_name): + download_status_text = download_model(model_name, force_download=True) + return [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), download_status_text] + + def cancel_download(): + return [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), ""] + + with gr.Row(): + confirm_button = gr.Button("Download Anyway", visible=False) + cancel_button = gr.Button("Cancel", visible=False) + + download_button.click(show_confirm_cancel, inputs=model_dropdown, outputs=[confirm_button, download_button, cancel_button]) + confirm_button.click(confirm_download, inputs=model_dropdown, outputs=[confirm_button, download_button, cancel_button, download_status]) + cancel_button.click(cancel_download, inputs=None, outputs=[confirm_button, download_button, cancel_button, download_status]) + + ##################################### + # Voices Editor - Unique to Parler # + ##################################### + # Load voices from the JSON file + def load_voices(): + voices_file = os.path.join(main_dir, "system", "tts_engines", "parler", "parler_voices.json") + if os.path.exists(voices_file): + with open(voices_file, "r") as f: + return json.load(f) + else: + return {"voices": []} + + # Save voices to the JSON file + def save_voices(voices): + voices_file = os.path.join(main_dir, "system", "tts_engines", "parler", "parler_voices.json") + with open(voices_file, "w") as f: + json.dump(voices, f, indent=2) + + # Convert voices to a list for display + def voices_to_list(voices): + return [f"{i+1}. {voice['voice_name']} - {voice['description']}" for i, voice in enumerate(voices["voices"])] + + # Populate text boxes with selected voice data + def populate_voice_data(selected): + if selected: + index = int(selected.split(".")[0]) - 1 + voices = load_voices() + selected_voice = voices["voices"][index] + return selected_voice["voice_name"], selected_voice["description"], index, gr.update(interactive=False), "Save Voice" + return "", "", -1, gr.update(interactive=True), "Add/Save Voice" + + # Add or update voice + def add_update_voice(voice_name, description, selected_index, button_label): + voices = load_voices() + if selected_index >= 0: + voices["voices"][selected_index]["description"] = description + else: + voices["voices"].append({"voice_name": voice_name, "description": description}) + save_voices(voices) + return (gr.Dropdown(choices=voices_to_list(voices), value=voices_to_list(voices)[0] if voices["voices"] else None), + "", "", -1, gr.update(interactive=True), "Add/Save Voice") + + # Delete selected voice + def delete_selected_voice(selected_index): + voices = load_voices() + if selected_index >= 0: + del voices["voices"][selected_index] + save_voices(voices) + return (gr.Dropdown(choices=voices_to_list(voices), value=voices_to_list(voices)[0] if voices["voices"] else None), + "", "", -1, gr.update(interactive=True), "Add/Save Voice") + + # Clear text boxes + def clear_text_boxes(): + return "", "", -1, gr.update(interactive=True), "Add/Save Voice" + + # Load initial voices + voices = load_voices() + + with gr.Tab("Voice Editor"): + with gr.Row(): + voice_name = gr.Textbox(label="Voice Name", scale=1) + description = gr.Textbox(label="Voice Description", lines=4, scale=3) + with gr.Row(): + add_update_button = gr.Button("Add/Save Voice") + delete_button = gr.Button("Delete Selected Voice") + clear_button = gr.Button("Clear Text Boxes") + with gr.Row(): + voice_list = gr.Dropdown( + choices=voices_to_list(voices), + label="Voices", + interactive=True, + value=voices_to_list(voices)[0] if voices["voices"] else None + ) + # Hidden state for selected index and button label + selected_index = gr.Number(value=-1, visible=False) + button_label = gr.State(value="Add/Save Voice") + + # Button click events + voice_list.change(populate_voice_data, inputs=voice_list, outputs=[voice_name, description, selected_index, voice_name, button_label]) + add_update_button.click(add_update_voice, inputs=[voice_name, description, selected_index, button_label], outputs=[voice_list, voice_name, description, selected_index, voice_name, button_label]) + delete_button.click(delete_selected_voice, inputs=selected_index, outputs=[voice_list, voice_name, description, selected_index, voice_name, button_label]) + clear_button.click(clear_text_boxes, outputs=[voice_name, description, selected_index, voice_name, button_label]) + + ################################################################################################### + # REQUIRED CHANGE # + # Add any engine specific help, bugs, issues, operating system specifc requirements/setup in here # + # Please use Markdown format, so gr.Markdown() with your markdown inside it. # + ################################################################################################### + with gr.Tab("Engine Help"): + with gr.Row(): + gr.Markdown(""" + ### 🟧 Where are the Parler models stored? + This extension will download the models to `/alltalk_tts/models/parler/` folder.
+ + ### 🟧 Where are the voices stored for parler models? + Parler is unlike any other TTS engine. It is actually mode like Stable Diffusion, where you write a text based description of what you want your image to look like, but in this case, you describe what you want the voice to sound like. This means you can have pretty much any voice you want, however, the downside to this is, there is no consistency of the voices, meaning that no 2x TTS generations will sound exactly like the same voice.
+ + ### 🟧 So how do I create my voices for Parler? + On the settings page for Parler, there is a Voice Editor tab. In there you can add/remove/ammend voices as you wish, or edit the `parler_voices.json` file stored in `system/tts_engines/parler/`. + + ### 🟧 Where are the outputs stored & Automatic output wav file deletion + Voice outputs are stored in `/alltalk_tts/outputs/`. You can configure automatic maintenance deletion of old wav files by setting `Del WAV's older than` in the global settings.
+ + > When `Disabled`, your output wav files will be left untouched.
+ > When set to a setting `1 Day` or greater, your output wav files older than that time period will be automatically deleted on start-up of AllTalk.
+ """) + gr.Markdown(""" + """) + + return app + +################################ +# REQUIRED CHANGE # +# Sets up the Gradio interface # +################################ +# This function sets up the Gradio interface for your TTS engine's settings page. +# You need to change the name of the function calls to match the names you set in the functions above. +# +# Specifically, you need to update the following: +# +# 1. The name of the function `xxxx_at_gradio_settings_page` to match your engine's name. +# For example, if your engine is named "mytts", the function should be named `mytts_at_gradio_settings_page`. +# +# 2. The name of the function call `xxxx_model_alltalk_settings(model_config_data)`. +# This should match the name you gave to the function that sets up the engine-specific settings page in Gradio. +# If you named that function `mytts_model_alltalk_settings`, then the call should be: +# `mytts_model_alltalk_settings(model_config_data)` +# +# After making these changes, this function will create and return the Gradio app for your TTS engine's settings page. +def parler_at_gradio_settings_page(model_config_data): + app = parler_model_alltalk_settings(model_config_data) + return app +def parler_at_gradio_settings_page(model_config_data): + app = parler_model_alltalk_settings(model_config_data) + return app \ No newline at end of file diff --git a/system/tts_engines/parler/parler_voices.json b/system/tts_engines/parler/parler_voices.json new file mode 100644 index 0000000..72b06e2 --- /dev/null +++ b/system/tts_engines/parler/parler_voices.json @@ -0,0 +1,96 @@ +{ + "voices": [ + { + "voice_name": "soothing_female", + "description": "A female speaker with a soft, soothing voice. Her tone is gentle and calming, with a slight reverb as if she is in a small, cozy room. She speaks slowly and clearly." + }, + { + "voice_name": "authoritative_male", + "description": "A male speaker with a strong, authoritative voice. His tone is firm and commanding, with a slight bass boost that adds to the depth. He speaks at a moderate pace." + }, + { + "voice_name": "friendly_young_male", + "description": "A young male speaker with a friendly, approachable voice. He has a slightly high-pitched tone and speaks with enthusiasm. The audio is clear and bright." + }, + { + "voice_name": "whispering_female", + "description": "A female speaker who whispers softly, creating an intimate and personal feel. The audio has a slight breathy quality, adding to the whispering effect." + }, + { + "voice_name": "robotic_male", + "description": "A male speaker with a robotic, synthetic voice. The tone is monotone and slightly mechanical, with clear and precise pronunciation." + }, + { + "voice_name": "dramatic_female", + "description": "A female speaker with a dramatic and expressive voice. She speaks with varying pitch and intensity, in a high-quality audio environment. Her pace varies to add emphasis." + }, + { + "voice_name": "casual_male", + "description": "A male speaker with a casual, conversational tone. He speaks in a relaxed manner, with natural pauses and inflections. The audio is clear and slightly ambient." + }, + { + "voice_name": "bright_young_female", + "description": "A young female speaker with a bright, cheerful voice. She speaks with a lot of enthusiasm and a slightly fast pace, in a clear and vibrant audio environment." + }, + { + "voice_name": "calm_older_male", + "description": "An older male speaker with a calm, soothing voice. His tone is deep and steady, with a slight reverb suggesting a large, quiet room. He speaks slowly and deliberately." + }, + { + "voice_name": "energetic_child", + "description": "A child speaker with a high-pitched, energetic voice. The tone is playful and lively, with clear and crisp audio quality. The speech pace is fast." + }, + { + "voice_name": "mysterious_female", + "description": "A female speaker with a low, mysterious voice. Her tone is hushed and slightly breathy, creating an air of secrecy. The audio has a slight echo effect." + }, + { + "voice_name": "confident_male", + "description": "A male speaker with a confident and assertive voice. His tone is strong and clear, with a moderate pace and slight emphasis on certain words to convey assurance." + }, + { + "voice_name": "gentle_young_female", + "description": "A young female speaker with a gentle, kind voice. Her tone is soft and sweet, with clear audio quality. She speaks at a slow and soothing pace." + }, + { + "voice_name": "excited_male", + "description": "A male speaker with an excited, enthusiastic voice. His tone is lively and energetic, with a fast pace and a lot of inflection. The audio is clear and vibrant." + }, + { + "voice_name": "professional_female", + "description": "A female speaker with a professional and articulate voice. Her tone is clear and measured, with precise pronunciation and a moderate pace. The audio is high-quality." + }, + { + "voice_name": "relaxed_male", + "description": "A male speaker with a relaxed and laid-back voice. His tone is warm and soothing, with a slight drawl. The audio has a comfortable and intimate feel." + }, + { + "voice_name": "melancholic_female", + "description": "A female speaker with a melancholic and introspective voice. Her tone is soft and slightly sad, with a slow and deliberate pace. The audio has a gentle reverb effect." + }, + { + "voice_name": "cheerful_child", + "description": "A child speaker with a cheerful and happy voice. The tone is high-pitched and playful, with a fast pace and clear audio quality. The speech is lively and energetic." + }, + { + "voice_name": "serious_male", + "description": "A male speaker with a serious and formal voice. His tone is deep and authoritative, with a slow and deliberate pace. The audio is clear and slightly reverberant." + }, + { + "voice_name": "enthusiastic_female", + "description": "A female speaker with an enthusiastic and lively voice. Her tone is bright and energetic, with a fast pace and a lot of inflection. The audio is clear and vibrant." + }, + { + "voice_name": "soft_spoken_male", + "description": "A male speaker with a soft-spoken and gentle voice. His tone is warm and soothing, with a slow and measured pace. The audio has a slight ambient quality." + }, + { + "voice_name": "deep_female", + "description": "A female speaker with a deep and resonant voice. Her tone is strong and authoritative, with a slow and deliberate pace. The audio is clear and slightly reverberant." + }, + { + "voice_name": "lighthearted_male", + "description": "A male speaker with a lighthearted and cheerful voice. His tone is warm and friendly, with a moderate pace and natural inflection. The audio is clear and bright." + } + ] +} \ No newline at end of file diff --git a/system/tts_engines/piper/available_models.json b/system/tts_engines/piper/available_models.json new file mode 100644 index 0000000..746331b --- /dev/null +++ b/system/tts_engines/piper/available_models.json @@ -0,0 +1,838 @@ +{ + "first_start_model": "en_GB-jenny_dioco-medium", + "models": [ + { + "model_name": "ar_JO-kareem-low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/ar/ar_JO/kareem/low/ar_JO-kareem-low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/ar/ar_JO/kareem/low/ar_JO-kareem-low.onnx.json?download=true" + ] + }, + { + "model_name": "ar_JO-kareem-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/ar/ar_JO/kareem/medium/ar_JO-kareem-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/ar/ar_JO/kareem/medium/ar_JO-kareem-medium.onnx.json?download=true" + ] + }, + { + "model_name": "ca_ES-upc_ona-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/ca/ca_ES/upc_ona/medium/ca_ES-upc_ona-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/ca/ca_ES/upc_ona/medium/ca_ES-upc_ona-medium.onnx.json?download=true" + ] + }, + { + "model_name": "ca_ES-upc_ona-x_low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/ca/ca_ES/upc_ona/x_low/ca_ES-upc_ona-x_low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/ca/ca_ES/upc_ona/x_low/ca_ES-upc_ona-x_low.onnx.json?download=true" + ] + }, + { + "model_name": "ca_ES-upc_pau-x_low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/ca/ca_ES/upc_pau/x_low/ca_ES-upc_pau-x_low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/ca/ca_ES/upc_pau/x_low/ca_ES-upc_pau-x_low.onnx.json?download=true" + ] + }, + { + "model_name": "cs_CZ-jirka-low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/cs/cs_CZ/jirka/low/cs_CZ-jirka-low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/cs/cs_CZ/jirka/low/cs_CZ-jirka-low.onnx.json?download=true" + ] + }, + { + "model_name": "cs_CZ-jirka-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/cs/cs_CZ/jirka/medium/cs_CZ-jirka-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/cs/cs_CZ/jirka/medium/cs_CZ-jirka-medium.onnx.json?download=true" + ] + }, + { + "model_name": "da_DK-talesyntese-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/da/da_DK/talesyntese/medium/da_DK-talesyntese-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/da/da_DK/talesyntese/medium/da_DK-talesyntese-medium.onnx.json?download=true" + ] + }, + { + "model_name": "de_DE-eva_k-x_low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/de/de_DE/eva_k/x_low/de_DE-eva_k-x_low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/de/de_DE/eva_k/x_low/de_DE-eva_k-x_low.onnx.json?download=true" + ] + }, + { + "model_name": "de_DE-karlsson-low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/de/de_DE/karlsson/low/de_DE-karlsson-low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/de/de_DE/karlsson/low/de_DE-karlsson-low.onnx.json?download=true" + ] + }, + { + "model_name": "de_DE-kerstin-low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/de/de_DE/kerstin/low/de_DE-kerstin-low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/de/de_DE/kerstin/low/de_DE-kerstin-low.onnx.json?download=true" + ] + }, + { + "model_name": "de_DE-mls-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/de/de_DE/mls/medium/de_DE-mls-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/de/de_DE/mls/medium/de_DE-mls-medium.onnx.json?download=true" + ] + }, + { + "model_name": "de_DE-pavoque-low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/de/de_DE/pavoque/low/de_DE-pavoque-low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/de/de_DE/pavoque/low/de_DE-pavoque-low.onnx.json?download=true" + ] + }, + { + "model_name": "de_DE-ramona-low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/de/de_DE/ramona/low/de_DE-ramona-low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/de/de_DE/ramona/low/de_DE-ramona-low.onnx.json?download=true" + ] + }, + { + "model_name": "de_DE-thorsten-high", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/de/de_DE/thorsten/high/de_DE-thorsten-high.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/de/de_DE/thorsten/high/de_DE-thorsten-high.onnx.json?download=true" + ] + }, + { + "model_name": "de_DE-thorsten-low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/de/de_DE/thorsten/low/de_DE-thorsten-low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/de/de_DE/thorsten/low/de_DE-thorsten-low.onnx.json?download=true" + ] + }, + { + "model_name": "de_DE-thorsten-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/de/de_DE/thorsten/medium/de_DE-thorsten-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/de/de_DE/thorsten/medium/de_DE-thorsten-medium.onnx.json?download=true" + ] + }, + { + "model_name": "de_DE-thorsten_emotional-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/de/de_DE/thorsten_emotional/medium/de_DE-thorsten_emotional-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/de/de_DE/thorsten_emotional/medium/de_DE-thorsten_emotional-medium.onnx.json?download=true" + ] + }, + { + "model_name": "el_GR-rapunzelina-low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/el/el_GR/rapunzelina/low/el_GR-rapunzelina-low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/el/el_GR/rapunzelina/low/el_GR-rapunzelina-low.onnx.json?download=true" + ] + }, + { + "model_name": "en_GB-alan-low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_GB/alan/low/en_GB-alan-low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_GB/alan/low/en_GB-alan-low.onnx.json?download=true" + ] + }, + { + "model_name": "en_GB-alan-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_GB/alan/medium/en_GB-alan-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_GB/alan/medium/en_GB-alan-medium.onnx.json?download=true" + ] + }, + { + "model_name": "en_GB-alba-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_GB/alba/medium/en_GB-alba-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_GB/alba/medium/en_GB-alba-medium.onnx.json?download=true" + ] + }, + { + "model_name": "en_GB-aru-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_GB/aru/medium/en_GB-aru-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_GB/aru/medium/en_GB-aru-medium.onnx.json?download=true" + ] + }, + { + "model_name": "en_GB-cori-high", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_GB/cori/high/en_GB-cori-high.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_GB/cori/high/en_GB-cori-high.onnx.json?download=true" + ] + }, + { + "model_name": "en_GB-cori-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_GB/cori/medium/en_GB-cori-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_GB/cori/medium/en_GB-cori-medium.onnx.json?download=true" + ] + }, + { + "model_name": "en_GB-jenny_dioco-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_GB/jenny_dioco/medium/en_GB-jenny_dioco-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_GB/jenny_dioco/medium/en_GB-jenny_dioco-medium.onnx.json?download=true" + ] + }, + { + "model_name": "en_GB-northern_english_male-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_GB/northern_english_male/medium/en_GB-northern_english_male-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_GB/northern_english_male/medium/en_GB-northern_english_male-medium.onnx.json?download=true" + ] + }, + { + "model_name": "en_GB-semaine-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_GB/semaine/medium/en_GB-semaine-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_GB/semaine/medium/en_GB-semaine-medium.onnx.json?download=true" + ] + }, + { + "model_name": "en_GB-southern_english_female-low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_GB/southern_english_female/low/en_GB-southern_english_female-low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_GB/southern_english_female/low/en_GB-southern_english_female-low.onnx.json?download=true" + ] + }, + { + "model_name": "en_GB-vctk-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_GB/vctk/medium/en_GB-vctk-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_GB/vctk/medium/en_GB-vctk-medium.onnx.json?download=true" + ] + }, + { + "model_name": "en_US-amy-low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/amy/low/en_US-amy-low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/amy/low/en_US-amy-low.onnx.json?download=true" + ] + }, + { + "model_name": "en_US-amy-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/amy/medium/en_US-amy-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/amy/medium/en_US-amy-medium.onnx.json?download=true" + ] + }, + { + "model_name": "en_US-arctic-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/arctic/medium/en_US-arctic-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/arctic/medium/en_US-arctic-medium.onnx.json?download=true" + ] + }, + { + "model_name": "en_US-danny-low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/danny/low/en_US-danny-low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/danny/low/en_US-danny-low.onnx.json?download=true" + ] + }, + { + "model_name": "en_US-hfc_female-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/hfc_female/medium/en_US-hfc_female-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/hfc_female/medium/en_US-hfc_female-medium.onnx.json?download=true" + ] + }, + { + "model_name": "en_US-hfc_male-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/hfc_male/medium/en_US-hfc_male-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/hfc_male/medium/en_US-hfc_male-medium.onnx.json?download=true" + ] + }, + { + "model_name": "en_US-joe-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/joe/medium/en_US-joe-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/joe/medium/en_US-joe-medium.onnx.json?download=true" + ] + }, + { + "model_name": "en_US-kathleen-low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/kathleen/low/en_US-kathleen-low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/kathleen/low/en_US-kathleen-low.onnx.json?download=true" + ] + }, + { + "model_name": "en_US-kristin-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/kristin/medium/en_US-kristin-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/kristin/medium/en_US-kristin-medium.onnx.json?download=true" + ] + }, + { + "model_name": "en_US-kusal-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/kusal/medium/en_US-kusal-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/kusal/medium/en_US-kusal-medium.onnx.json?download=true" + ] + }, + { + "model_name": "en_US-l2arctic-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/l2arctic/medium/en_US-l2arctic-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/l2arctic/medium/en_US-l2arctic-medium.onnx.json?download=true" + ] + }, + { + "model_name": "en_US-lessac-high", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/lessac/high/en_US-lessac-high.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/lessac/high/en_US-lessac-high.onnx.json?download=true" + ] + }, + { + "model_name": "en_US-lessac-low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/lessac/low/en_US-lessac-low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/lessac/low/en_US-lessac-low.onnx.json?download=true" + ] + }, + { + "model_name": "en_US-lessac-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/lessac/medium/en_US-lessac-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/lessac/medium/en_US-lessac-medium.onnx.json?download=true" + ] + }, + { + "model_name": "en_US-libritts-high", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/libritts/high/en_US-libritts-high.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/libritts/high/en_US-libritts-high.onnx.json?download=true" + ] + }, + { + "model_name": "en_US-libritts_r-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx.json?download=true" + ] + }, + { + "model_name": "en_US-ljspeech-high", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/ljspeech/high/en_US-ljspeech-high.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/ljspeech/high/en_US-ljspeech-high.onnx.json?download=true" + ] + }, + { + "model_name": "en_US-ljspeech-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/ljspeech/medium/en_US-ljspeech-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/ljspeech/medium/en_US-ljspeech-medium.onnx.json?download=true" + ] + }, + { + "model_name": "en_US-ryan-high", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/ryan/high/en_US-ryan-high.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/ryan/high/en_US-ryan-high.onnx.json?download=true" + ] + }, + { + "model_name": "en_US-ryan-low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/ryan/low/en_US-ryan-low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/ryan/low/en_US-ryan-low.onnx.json?download=true" + ] + }, + { + "model_name": "en_US-ryan-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/ryan/medium/en_US-ryan-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/ryan/medium/en_US-ryan-medium.onnx.json?download=true" + ] + }, + { + "model_name": "es_ES-carlfm-x_low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/es/es_ES/carlfm/x_low/es_ES-carlfm-x_low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/es/es_ES/carlfm/x_low/es_ES-carlfm-x_low.onnx.json?download=true" + ] + }, + { + "model_name": "es_ES-davefx-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/es/es_ES/davefx/medium/es_ES-davefx-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/es/es_ES/davefx/medium/es_ES-davefx-medium.onnx.json?download=true" + ] + }, + { + "model_name": "es_ES-mls_10246-low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/es/es_ES/mls_10246/low/es_ES-mls_10246-low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/es/es_ES/mls_10246/low/es_ES-mls_10246-low.onnx.json?download=true" + ] + }, + { + "model_name": "es_ES-mls_9972-low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/es/es_ES/mls_9972/low/es_ES-mls_9972-low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/es/es_ES/mls_9972/low/es_ES-mls_9972-low.onnx.json?download=true" + ] + }, + { + "model_name": "es_ES-sharvard-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/es/es_ES/sharvard/medium/es_ES-sharvard-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/es/es_ES/sharvard/medium/es_ES-sharvard-medium.onnx.json?download=true" + ] + }, + { + "model_name": "es_MX-ald-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/es/es_MX/ald/medium/es_MX-ald-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/es/es_MX/ald/medium/es_MX-ald-medium.onnx.json?download=true" + ] + }, + { + "model_name": "es_MX-claude-high", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/es/es_MX/claude/high/es_MX-claude-high.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/es/es_MX/claude/high/es_MX-claude-high.onnx.json?download=true" + ] + }, + { + "model_name": "fa_IR-amir-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/fa/fa_IR/amir/medium/fa_IR-amir-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/fa/fa_IR/amir/medium/fa_IR-amir-medium.onnx.json?download=true" + ] + }, + { + "model_name": "fa_IR-gyro-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/fa/fa_IR/gyro/medium/fa_IR-gyro-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/fa/fa_IR/gyro/medium/fa_IR-gyro-medium.onnx.json?download=true" + ] + }, + { + "model_name": "fi_FI-harri-low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/fi/fi_FI/harri/low/fi_FI-harri-low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/fi/fi_FI/harri/low/fi_FI-harri-low.onnx.json?download=true" + ] + }, + { + "model_name": "fi_FI-harri-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/fi/fi_FI/harri/medium/fi_FI-harri-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/fi/fi_FI/harri/medium/fi_FI-harri-medium.onnx.json?download=true" + ] + }, + { + "model_name": "fr_FR-gilles-low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/fr/fr_FR/gilles/low/fr_FR-gilles-low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/fr/fr_FR/gilles/low/fr_FR-gilles-low.onnx.json?download=true" + ] + }, + { + "model_name": "fr_FR-mls-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/fr/fr_FR/mls/medium/fr_FR-mls-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/fr/fr_FR/mls/medium/fr_FR-mls-medium.onnx.json?download=true" + ] + }, + { + "model_name": "fr_FR-mls_1840-low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/fr/fr_FR/mls_1840/low/fr_FR-mls_1840-low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/fr/fr_FR/mls_1840/low/fr_FR-mls_1840-low.onnx.json?download=true" + ] + }, + { + "model_name": "fr_FR-siwis-low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/fr/fr_FR/siwis/low/fr_FR-siwis-low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/fr/fr_FR/siwis/low/fr_FR-siwis-low.onnx.json?download=true" + ] + }, + { + "model_name": "fr_FR-siwis-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/fr/fr_FR/siwis/medium/fr_FR-siwis-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/fr/fr_FR/siwis/medium/fr_FR-siwis-medium.onnx.json?download=true" + ] + }, + { + "model_name": "fr_FR-tom-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/fr/fr_FR/tom/medium/fr_FR-tom-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/fr/fr_FR/tom/medium/fr_FR-tom-medium.onnx.json?download=true" + ] + }, + { + "model_name": "fr_FR-upmc-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/fr/fr_FR/upmc/medium/fr_FR-upmc-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/fr/fr_FR/upmc/medium/fr_FR-upmc-medium.onnx.json?download=true" + ] + }, + { + "model_name": "hu_HU-anna-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/hu/hu_HU/anna/medium/hu_HU-anna-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/hu/hu_HU/anna/medium/hu_HU-anna-medium.onnx.json?download=true" + ] + }, + { + "model_name": "hu_HU-berta-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/hu/hu_HU/berta/medium/hu_HU-berta-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/hu/hu_HU/berta/medium/hu_HU-berta-medium.onnx.json?download=true" + ] + }, + { + "model_name": "hu_HU-imre-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/hu/hu_HU/imre/medium/hu_HU-imre-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/hu/hu_HU/imre/medium/hu_HU-imre-medium.onnx.json?download=true" + ] + }, + { + "model_name": "is_IS-bui-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/is/is_IS/bui/medium/is_IS-bui-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/is/is_IS/bui/medium/is_IS-bui-medium.onnx.json?download=true" + ] + }, + { + "model_name": "is_IS-salka-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/is/is_IS/salka/medium/is_IS-salka-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/is/is_IS/salka/medium/is_IS-salka-medium.onnx.json?download=true" + ] + }, + { + "model_name": "is_IS-steinn-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/is/is_IS/steinn/medium/is_IS-steinn-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/is/is_IS/steinn/medium/is_IS-steinn-medium.onnx.json?download=true" + ] + }, + { + "model_name": "is_IS-ugla-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/is/is_IS/ugla/medium/is_IS-ugla-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/is/is_IS/ugla/medium/is_IS-ugla-medium.onnx.json?download=true" + ] + }, + { + "model_name": "it_IT-riccardo-x_low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/it/it_IT/riccardo/x_low/it_IT-riccardo-x_low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/it/it_IT/riccardo/x_low/it_IT-riccardo-x_low.onnx.json?download=true" + ] + }, + { + "model_name": "ka_GE-natia-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/ka/ka_GE/natia/medium/ka_GE-natia-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/ka/ka_GE/natia/medium/ka_GE-natia-medium.onnx.json?download=true" + ] + }, + { + "model_name": "kk_KZ-iseke-x_low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/kk/kk_KZ/iseke/x_low/kk_KZ-iseke-x_low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/kk/kk_KZ/iseke/x_low/kk_KZ-iseke-x_low.onnx.json?download=true" + ] + }, + { + "model_name": "kk_KZ-issai-high", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/kk/kk_KZ/issai/high/kk_KZ-issai-high.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/kk/kk_KZ/issai/high/kk_KZ-issai-high.onnx.json?download=true" + ] + }, + { + "model_name": "kk_KZ-raya-x_low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/kk/kk_KZ/raya/x_low/kk_KZ-raya-x_low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/kk/kk_KZ/raya/x_low/kk_KZ-raya-x_low.onnx.json?download=true" + ] + }, + { + "model_name": "lb_LU-marylux-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/lb/lb_LU/marylux/medium/lb_LU-marylux-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/lb/lb_LU/marylux/medium/lb_LU-marylux-medium.onnx.json?download=true" + ] + }, + { + "model_name": "ne_NP-google-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/ne/ne_NP/google/medium/ne_NP-google-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/ne/ne_NP/google/medium/ne_NP-google-medium.onnx.json?download=true" + ] + }, + { + "model_name": "ne_NP-google-x_low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/ne/ne_NP/google/x_low/ne_NP-google-x_low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/ne/ne_NP/google/x_low/ne_NP-google-x_low.onnx.json?download=true" + ] + }, + { + "model_name": "nl_BE-nathalie-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/nl/nl_BE/nathalie/medium/nl_BE-nathalie-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/nl/nl_BE/nathalie/medium/nl_BE-nathalie-medium.onnx.json?download=true" + ] + }, + { + "model_name": "nl_BE-nathalie-x_low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/nl/nl_BE/nathalie/x_low/nl_BE-nathalie-x_low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/nl/nl_BE/nathalie/x_low/nl_BE-nathalie-x_low.onnx.json?download=true" + ] + }, + { + "model_name": "nl_BE-rdh-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/nl/nl_BE/rdh/medium/nl_BE-rdh-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/nl/nl_BE/rdh/medium/nl_BE-rdh-medium.onnx.json?download=true" + ] + }, + { + "model_name": "nl_BE-rdh-x_low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/nl/nl_BE/rdh/x_low/nl_BE-rdh-x_low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/nl/nl_BE/rdh/x_low/nl_BE-rdh-x_low.onnx.json?download=true" + ] + }, + { + "model_name": "nl_NL-mls-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/nl/nl_NL/mls/medium/nl_NL-mls-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/nl/nl_NL/mls/medium/nl_NL-mls-medium.onnx.json?download=true" + ] + }, + { + "model_name": "nl_NL-mls_5809-low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/nl/nl_NL/mls_5809/low/nl_NL-mls_5809-low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/nl/nl_NL/mls_5809/low/nl_NL-mls_5809-low.onnx.json?download=true" + ] + }, + { + "model_name": "nl_NL-mls_7432-low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/nl/nl_NL/mls_7432/low/nl_NL-mls_7432-low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/nl/nl_NL/mls_7432/low/nl_NL-mls_7432-low.onnx.json?download=true" + ] + }, + { + "model_name": "no_NO-talesyntese-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/no/no_NO/talesyntese/medium/no_NO-talesyntese-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/no/no_NO/talesyntese/medium/no_NO-talesyntese-medium.onnx.json?download=true" + ] + }, + { + "model_name": "pl_PL-darkman-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/pl/pl_PL/darkman/medium/pl_PL-darkman-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/pl/pl_PL/darkman/medium/pl_PL-darkman-medium.onnx.json?download=true" + ] + }, + { + "model_name": "pl_PL-gosia-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/pl/pl_PL/gosia/medium/pl_PL-gosia-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/pl/pl_PL/gosia/medium/pl_PL-gosia-medium.onnx.json?download=true" + ] + }, + { + "model_name": "pl_PL-mc_speech-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/pl/pl_PL/mc_speech/medium/pl_PL-mc_speech-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/pl/pl_PL/mc_speech/medium/pl_PL-mc_speech-medium.onnx.json?download=true" + ] + }, + { + "model_name": "pl_PL-mls_6892-low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/pl/pl_PL/mls_6892/low/pl_PL-mls_6892-low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/pl/pl_PL/mls_6892/low/pl_PL-mls_6892-low.onnx.json?download=true" + ] + }, + { + "model_name": "pt_BR-edresson-low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/pt/pt_BR/edresson/low/pt_BR-edresson-low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/pt/pt_BR/edresson/low/pt_BR-edresson-low.onnx.json?download=true" + ] + }, + { + "model_name": "pt_BR-faber-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/pt/pt_BR/faber/medium/pt_BR-faber-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/pt/pt_BR/faber/medium/pt_BR-faber-medium.onnx.json?download=true" + ] + }, + { + "model_name": "pt_PT-tug\u00e3o-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/pt/pt_PT/tug\u00e3o/medium/pt_PT-tug\u00e3o-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/pt/pt_PT/tug\u00e3o/medium/pt_PT-tug\u00e3o-medium.onnx.json?download=true" + ] + }, + { + "model_name": "ro_RO-mihai-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/ro/ro_RO/mihai/medium/ro_RO-mihai-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/ro/ro_RO/mihai/medium/ro_RO-mihai-medium.onnx.json?download=true" + ] + }, + { + "model_name": "ru_RU-denis-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/ru/ru_RU/denis/medium/ru_RU-denis-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/ru/ru_RU/denis/medium/ru_RU-denis-medium.onnx.json?download=true" + ] + }, + { + "model_name": "ru_RU-dmitri-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/ru/ru_RU/dmitri/medium/ru_RU-dmitri-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/ru/ru_RU/dmitri/medium/ru_RU-dmitri-medium.onnx.json?download=true" + ] + }, + { + "model_name": "ru_RU-irina-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/ru/ru_RU/irina/medium/ru_RU-irina-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/ru/ru_RU/irina/medium/ru_RU-irina-medium.onnx.json?download=true" + ] + }, + { + "model_name": "ru_RU-ruslan-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/ru/ru_RU/ruslan/medium/ru_RU-ruslan-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/ru/ru_RU/ruslan/medium/ru_RU-ruslan-medium.onnx.json?download=true" + ] + }, + { + "model_name": "sk_SK-lili-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/sk/sk_SK/lili/medium/sk_SK-lili-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/sk/sk_SK/lili/medium/sk_SK-lili-medium.onnx.json?download=true" + ] + }, + { + "model_name": "sl_SI-artur-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/sl/sl_SI/artur/medium/sl_SI-artur-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/sl/sl_SI/artur/medium/sl_SI-artur-medium.onnx.json?download=true" + ] + }, + { + "model_name": "sr_RS-serbski_institut-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/sr/sr_RS/serbski_institut/medium/sr_RS-serbski_institut-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/sr/sr_RS/serbski_institut/medium/sr_RS-serbski_institut-medium.onnx.json?download=true" + ] + }, + { + "model_name": "sv_SE-nst-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/sv/sv_SE/nst/medium/sv_SE-nst-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/sv/sv_SE/nst/medium/sv_SE-nst-medium.onnx.json?download=true" + ] + }, + { + "model_name": "sw_CD-lanfrica-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/sw/sw_CD/lanfrica/medium/sw_CD-lanfrica-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/sw/sw_CD/lanfrica/medium/sw_CD-lanfrica-medium.onnx.json?download=true" + ] + }, + { + "model_name": "tr_TR-dfki-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/tr/tr_TR/dfki/medium/tr_TR-dfki-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/tr/tr_TR/dfki/medium/tr_TR-dfki-medium.onnx.json?download=true" + ] + }, + { + "model_name": "tr_TR-fahrettin-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/tr/tr_TR/fahrettin/medium/tr_TR-fahrettin-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/tr/tr_TR/fahrettin/medium/tr_TR-fahrettin-medium.onnx.json?download=true" + ] + }, + { + "model_name": "tr_TR-fettah-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/tr/tr_TR/fettah/medium/tr_TR-fettah-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/tr/tr_TR/fettah/medium/tr_TR-fettah-medium.onnx.json?download=true" + ] + }, + { + "model_name": "uk_UA-lada-x_low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/uk/uk_UA/lada/x_low/uk_UA-lada-x_low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/uk/uk_UA/lada/x_low/uk_UA-lada-x_low.onnx.json?download=true" + ] + }, + { + "model_name": "uk_UA-ukrainian_tts-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/uk/uk_UA/ukrainian_tts/medium/uk_UA-ukrainian_tts-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/uk/uk_UA/ukrainian_tts/medium/uk_UA-ukrainian_tts-medium.onnx.json?download=true" + ] + }, + { + "model_name": "vi_VN-25hours_single-low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/vi/vi_VN/25hours_single/low/vi_VN-25hours_single-low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/vi/vi_VN/25hours_single/low/vi_VN-25hours_single-low.onnx.json?download=true" + ] + }, + { + "model_name": "vi_VN-vais1000-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/vi/vi_VN/vais1000/medium/vi_VN-vais1000-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/vi/vi_VN/vais1000/medium/vi_VN-vais1000-medium.onnx.json?download=true" + ] + }, + { + "model_name": "vi_VN-vivos-x_low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/vi/vi_VN/vivos/x_low/vi_VN-vivos-x_low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/vi/vi_VN/vivos/x_low/vi_VN-vivos-x_low.onnx.json?download=true" + ] + }, + { + "model_name": "zh_CN-huayan-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/zh/zh_CN/huayan/medium/zh_CN-huayan-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/zh/zh_CN/huayan/medium/zh_CN-huayan-medium.onnx.json?download=true" + ] + }, + { + "model_name": "zh_CN-huayan-x_low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/zh/zh_CN/huayan/x_low/zh_CN-huayan-x_low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/zh/zh_CN/huayan/x_low/zh_CN-huayan-x_low.onnx.json?download=true" + ] + } + ] +} \ No newline at end of file diff --git a/system/tts_engines/piper/engine/espeak-ng-data/af_dict b/system/tts_engines/piper/engine/espeak-ng-data/af_dict new file mode 100644 index 0000000..b6259ef Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/af_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/am_dict b/system/tts_engines/piper/engine/espeak-ng-data/am_dict new file mode 100644 index 0000000..b2b0442 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/am_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/an_dict b/system/tts_engines/piper/engine/espeak-ng-data/an_dict new file mode 100644 index 0000000..d10d32c Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/an_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/ar_dict b/system/tts_engines/piper/engine/espeak-ng-data/ar_dict new file mode 100644 index 0000000..108ff2b Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/ar_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/as_dict b/system/tts_engines/piper/engine/espeak-ng-data/as_dict new file mode 100644 index 0000000..6bd0e60 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/as_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/az_dict b/system/tts_engines/piper/engine/espeak-ng-data/az_dict new file mode 100644 index 0000000..77ca091 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/az_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/ba_dict b/system/tts_engines/piper/engine/espeak-ng-data/ba_dict new file mode 100644 index 0000000..f175c68 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/ba_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/be_dict b/system/tts_engines/piper/engine/espeak-ng-data/be_dict new file mode 100644 index 0000000..39d7434 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/be_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/bg_dict b/system/tts_engines/piper/engine/espeak-ng-data/bg_dict new file mode 100644 index 0000000..ecd7f4a Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/bg_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/bn_dict b/system/tts_engines/piper/engine/espeak-ng-data/bn_dict new file mode 100644 index 0000000..627f13e Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/bn_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/bpy_dict b/system/tts_engines/piper/engine/espeak-ng-data/bpy_dict new file mode 100644 index 0000000..735496f Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/bpy_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/bs_dict b/system/tts_engines/piper/engine/espeak-ng-data/bs_dict new file mode 100644 index 0000000..71a3893 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/bs_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/ca_dict b/system/tts_engines/piper/engine/espeak-ng-data/ca_dict new file mode 100644 index 0000000..f134d4e Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/ca_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/chr_dict b/system/tts_engines/piper/engine/espeak-ng-data/chr_dict new file mode 100644 index 0000000..489aa57 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/chr_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/cmn_dict b/system/tts_engines/piper/engine/espeak-ng-data/cmn_dict new file mode 100644 index 0000000..f284cab Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/cmn_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/cs_dict b/system/tts_engines/piper/engine/espeak-ng-data/cs_dict new file mode 100644 index 0000000..cf916da Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/cs_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/cv_dict b/system/tts_engines/piper/engine/espeak-ng-data/cv_dict new file mode 100644 index 0000000..79311d9 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/cv_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/cy_dict b/system/tts_engines/piper/engine/espeak-ng-data/cy_dict new file mode 100644 index 0000000..c4f738b Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/cy_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/da_dict b/system/tts_engines/piper/engine/espeak-ng-data/da_dict new file mode 100644 index 0000000..08e9089 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/da_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/de_dict b/system/tts_engines/piper/engine/espeak-ng-data/de_dict new file mode 100644 index 0000000..6f7697c Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/de_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/el_dict b/system/tts_engines/piper/engine/espeak-ng-data/el_dict new file mode 100644 index 0000000..e6831f0 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/el_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/en_dict b/system/tts_engines/piper/engine/espeak-ng-data/en_dict new file mode 100644 index 0000000..b05b861 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/en_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/eo_dict b/system/tts_engines/piper/engine/espeak-ng-data/eo_dict new file mode 100644 index 0000000..4ea54fe Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/eo_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/es_dict b/system/tts_engines/piper/engine/espeak-ng-data/es_dict new file mode 100644 index 0000000..c810ac7 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/es_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/et_dict b/system/tts_engines/piper/engine/espeak-ng-data/et_dict new file mode 100644 index 0000000..f336d14 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/et_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/eu_dict b/system/tts_engines/piper/engine/espeak-ng-data/eu_dict new file mode 100644 index 0000000..ff71e05 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/eu_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/fa_dict b/system/tts_engines/piper/engine/espeak-ng-data/fa_dict new file mode 100644 index 0000000..c67fb89 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/fa_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/fi_dict b/system/tts_engines/piper/engine/espeak-ng-data/fi_dict new file mode 100644 index 0000000..a602847 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/fi_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/fr_dict b/system/tts_engines/piper/engine/espeak-ng-data/fr_dict new file mode 100644 index 0000000..560a8c6 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/fr_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/ga_dict b/system/tts_engines/piper/engine/espeak-ng-data/ga_dict new file mode 100644 index 0000000..65338e2 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/ga_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/gd_dict b/system/tts_engines/piper/engine/espeak-ng-data/gd_dict new file mode 100644 index 0000000..3ce4b95 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/gd_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/gn_dict b/system/tts_engines/piper/engine/espeak-ng-data/gn_dict new file mode 100644 index 0000000..ee7fa4b Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/gn_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/grc_dict b/system/tts_engines/piper/engine/espeak-ng-data/grc_dict new file mode 100644 index 0000000..1d9f374 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/grc_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/gu_dict b/system/tts_engines/piper/engine/espeak-ng-data/gu_dict new file mode 100644 index 0000000..245afbb Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/gu_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/hak_dict b/system/tts_engines/piper/engine/espeak-ng-data/hak_dict new file mode 100644 index 0000000..38adf87 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/hak_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/haw_dict b/system/tts_engines/piper/engine/espeak-ng-data/haw_dict new file mode 100644 index 0000000..a78245c Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/haw_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/he_dict b/system/tts_engines/piper/engine/espeak-ng-data/he_dict new file mode 100644 index 0000000..e1037a6 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/he_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/hi_dict b/system/tts_engines/piper/engine/espeak-ng-data/hi_dict new file mode 100644 index 0000000..c612100 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/hi_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/hr_dict b/system/tts_engines/piper/engine/espeak-ng-data/hr_dict new file mode 100644 index 0000000..0ecec2d Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/hr_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/ht_dict b/system/tts_engines/piper/engine/espeak-ng-data/ht_dict new file mode 100644 index 0000000..ada2b70 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/ht_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/hu_dict b/system/tts_engines/piper/engine/espeak-ng-data/hu_dict new file mode 100644 index 0000000..560dc2a Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/hu_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/hy_dict b/system/tts_engines/piper/engine/espeak-ng-data/hy_dict new file mode 100644 index 0000000..7fb0fe3 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/hy_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/ia_dict b/system/tts_engines/piper/engine/espeak-ng-data/ia_dict new file mode 100644 index 0000000..2177d15 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/ia_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/id_dict b/system/tts_engines/piper/engine/espeak-ng-data/id_dict new file mode 100644 index 0000000..6e71d72 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/id_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/intonations b/system/tts_engines/piper/engine/espeak-ng-data/intonations new file mode 100644 index 0000000..ca2d4b3 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/intonations differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/io_dict b/system/tts_engines/piper/engine/espeak-ng-data/io_dict new file mode 100644 index 0000000..e690ae8 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/io_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/is_dict b/system/tts_engines/piper/engine/espeak-ng-data/is_dict new file mode 100644 index 0000000..935799a Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/is_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/it_dict b/system/tts_engines/piper/engine/espeak-ng-data/it_dict new file mode 100644 index 0000000..aef22eb Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/it_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/ja_dict b/system/tts_engines/piper/engine/espeak-ng-data/ja_dict new file mode 100644 index 0000000..19c63a3 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/ja_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/jbo_dict b/system/tts_engines/piper/engine/espeak-ng-data/jbo_dict new file mode 100644 index 0000000..938b323 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/jbo_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/ka_dict b/system/tts_engines/piper/engine/espeak-ng-data/ka_dict new file mode 100644 index 0000000..a43848b Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/ka_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/kk_dict b/system/tts_engines/piper/engine/espeak-ng-data/kk_dict new file mode 100644 index 0000000..7e02e22 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/kk_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/kl_dict b/system/tts_engines/piper/engine/espeak-ng-data/kl_dict new file mode 100644 index 0000000..10a0566 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/kl_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/kn_dict b/system/tts_engines/piper/engine/espeak-ng-data/kn_dict new file mode 100644 index 0000000..6a41a8d Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/kn_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/ko_dict b/system/tts_engines/piper/engine/espeak-ng-data/ko_dict new file mode 100644 index 0000000..296dffb Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/ko_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/kok_dict b/system/tts_engines/piper/engine/espeak-ng-data/kok_dict new file mode 100644 index 0000000..1f2813c Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/kok_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/ku_dict b/system/tts_engines/piper/engine/espeak-ng-data/ku_dict new file mode 100644 index 0000000..bf3d745 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/ku_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/ky_dict b/system/tts_engines/piper/engine/espeak-ng-data/ky_dict new file mode 100644 index 0000000..3ea39d9 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/ky_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/la_dict b/system/tts_engines/piper/engine/espeak-ng-data/la_dict new file mode 100644 index 0000000..3f30fd3 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/la_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/aav/vi b/system/tts_engines/piper/engine/espeak-ng-data/lang/aav/vi new file mode 100644 index 0000000..7808128 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/aav/vi @@ -0,0 +1,8 @@ +name Vietnamese (Northern) +language vi + +words 1 2 +pitch 95 175 + + +tone 100 225 800 100 2000 50 5400 75 8000 200 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/aav/vi-VN-x-central b/system/tts_engines/piper/engine/espeak-ng-data/lang/aav/vi-VN-x-central new file mode 100644 index 0000000..0defaca --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/aav/vi-VN-x-central @@ -0,0 +1,9 @@ +name Vietnamese (Central) +language vi-vn-x-central +phonemes vi-hue +dictrules 1 + +words 1 +pitch 82 118 //80 118 + voicing 90 //18 + flutter 20 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/aav/vi-VN-x-south b/system/tts_engines/piper/engine/espeak-ng-data/lang/aav/vi-VN-x-south new file mode 100644 index 0000000..4c32561 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/aav/vi-VN-x-south @@ -0,0 +1,9 @@ +name Vietnamese (Southern) +language vi-vn-x-south +phonemes vi-sgn +dictrules 2 + +words 1 +pitch 82 118 //80 118 + voicing 90 //18 + flutter 20 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/art/eo b/system/tts_engines/piper/engine/espeak-ng-data/lang/art/eo new file mode 100644 index 0000000..e47501f --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/art/eo @@ -0,0 +1,4 @@ +name Esperanto +language eo + +apostrophe 2 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/art/ia b/system/tts_engines/piper/engine/espeak-ng-data/lang/art/ia new file mode 100644 index 0000000..6a14728 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/art/ia @@ -0,0 +1,2 @@ +name Interlingua +language ia diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/art/io b/system/tts_engines/piper/engine/espeak-ng-data/lang/art/io new file mode 100644 index 0000000..c50f9fa --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/art/io @@ -0,0 +1,5 @@ +name Ido +language io +phonemes eo +status testing + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/art/jbo b/system/tts_engines/piper/engine/espeak-ng-data/lang/art/jbo new file mode 100644 index 0000000..9cf2f30 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/art/jbo @@ -0,0 +1,4 @@ +name Lojban +language jbo + +speed 80 // speed adjustment, percentage diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/art/lfn b/system/tts_engines/piper/engine/espeak-ng-data/lang/art/lfn new file mode 100644 index 0000000..db19040 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/art/lfn @@ -0,0 +1,8 @@ +name Lingua Franca Nova +language lfn + +phonemes base2 +l_unpronouncable 0 +numbers 2 3 + +stressLength 150 140 180 180 0 0 200 200 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/art/piqd b/system/tts_engines/piper/engine/espeak-ng-data/lang/art/piqd new file mode 100644 index 0000000..49367cd --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/art/piqd @@ -0,0 +1,5 @@ +name Klingon +language piqd +status testing +stressRule 3 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/art/py b/system/tts_engines/piper/engine/espeak-ng-data/lang/art/py new file mode 100644 index 0000000..850a34e --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/art/py @@ -0,0 +1,7 @@ +name Pyash +language py +maintainer Logan Streondj +status testing + +speed 80 // speed adjustment, percentage +stressRule 0 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/art/qdb b/system/tts_engines/piper/engine/espeak-ng-data/lang/art/qdb new file mode 100644 index 0000000..eb5ea36 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/art/qdb @@ -0,0 +1,6 @@ +name Lang Belta +language qdb + +numbers 4 3 + +replace 1 t ? diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/art/qya b/system/tts_engines/piper/engine/espeak-ng-data/lang/art/qya new file mode 100644 index 0000000..2b51581 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/art/qya @@ -0,0 +1,4 @@ +name Quenya +language qya +stressRule 2 +// rule=penultimate, with qya_rules for light penultimate syllables to move primary stress to the preceding (antepenultimate) syllable diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/art/sjn b/system/tts_engines/piper/engine/espeak-ng-data/lang/art/sjn new file mode 100644 index 0000000..f927bc9 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/art/sjn @@ -0,0 +1,4 @@ +name Sindarin +language sjn +stressRule 2 +// rule=penultimate, with sjn_rules for light penultimate syllables to move primary stress to the preceding (antepenultimate) syllable diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/azc/nci b/system/tts_engines/piper/engine/espeak-ng-data/lang/azc/nci new file mode 100644 index 0000000..f189637 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/azc/nci @@ -0,0 +1,6 @@ +name Nahuatl (Classical) +language nci + +intonation 3 +stressRule 2 +stressLength 190 190 200 200 0 0 220 240 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/bat/lt b/system/tts_engines/piper/engine/espeak-ng-data/lang/bat/lt new file mode 100644 index 0000000..7cdb193 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/bat/lt @@ -0,0 +1,2 @@ +name Lithuanian +language lt diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/bat/ltg b/system/tts_engines/piper/engine/espeak-ng-data/lang/bat/ltg new file mode 100644 index 0000000..1041d9e --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/bat/ltg @@ -0,0 +1,12 @@ +name Latgalian +language ltg +maintainer Valdis Vitolins +status testing +phonemes lv +dictionary lv +dictrules 2 // Setting for Latgalian pronunciation +words 0 2 +pitch 64 118 +tone 60 150 204 100 400 255 700 10 3000 255 +stressAmp 12 10 8 8 0 0 15 16 +stressLength 160 140 200 140 0 0 240 160 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/bat/lv b/system/tts_engines/piper/engine/espeak-ng-data/lang/bat/lv new file mode 100644 index 0000000..70eb61d --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/bat/lv @@ -0,0 +1,9 @@ +name Latvian +language lv +maintainer Valdis Vitolins +status mature +words 0 2 +pitch 67 123 +tone 60 150 204 100 400 255 700 10 3000 255 +stressAmp 11 8 11 9 0 0 14 12 +stressLength 160 120 200 130 0 0 230 180 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/bnt/sw b/system/tts_engines/piper/engine/espeak-ng-data/lang/bnt/sw new file mode 100644 index 0000000..d1a4db3 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/bnt/sw @@ -0,0 +1,4 @@ +name Swahili +language sw + +status testing diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/bnt/tn b/system/tts_engines/piper/engine/espeak-ng-data/lang/bnt/tn new file mode 100644 index 0000000..8b484e4 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/bnt/tn @@ -0,0 +1,4 @@ +name Setswana +language tn + +status testing diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/ccs/ka b/system/tts_engines/piper/engine/espeak-ng-data/lang/ccs/ka new file mode 100644 index 0000000..2a789b3 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/ccs/ka @@ -0,0 +1,3 @@ +name Georgian +language ka +lowercaseSentence // A period followed by a lowercase letter is considered a sentence (mkhedruli) diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/cel/cy b/system/tts_engines/piper/engine/espeak-ng-data/lang/cel/cy new file mode 100644 index 0000000..6d02132 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/cel/cy @@ -0,0 +1,4 @@ +name Welsh +language cy + +intonation 4 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/cel/ga b/system/tts_engines/piper/engine/espeak-ng-data/lang/cel/ga new file mode 100644 index 0000000..9fea2ae --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/cel/ga @@ -0,0 +1,4 @@ +name Gaelic (Irish) +language ga + +dictrules 1 // fix for eclipsis diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/cel/gd b/system/tts_engines/piper/engine/espeak-ng-data/lang/cel/gd new file mode 100644 index 0000000..e416f6f --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/cel/gd @@ -0,0 +1,4 @@ +name Gaelic (Scottish) +language gd + +status testing diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/cus/om b/system/tts_engines/piper/engine/espeak-ng-data/lang/cus/om new file mode 100644 index 0000000..1d6b396 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/cus/om @@ -0,0 +1,4 @@ +name Oromo +language om + +status testing diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/dra/kn b/system/tts_engines/piper/engine/espeak-ng-data/lang/dra/kn new file mode 100644 index 0000000..a32732c --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/dra/kn @@ -0,0 +1,5 @@ +name Kannada +language kn + +intonation 2 +//consonants 80 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/dra/ml b/system/tts_engines/piper/engine/espeak-ng-data/lang/dra/ml new file mode 100644 index 0000000..92394bb --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/dra/ml @@ -0,0 +1,5 @@ +name Malayalam +language ml + +intonation 2 +//consonants 80 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/dra/ta b/system/tts_engines/piper/engine/espeak-ng-data/lang/dra/ta new file mode 100644 index 0000000..aae6334 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/dra/ta @@ -0,0 +1,5 @@ +name Tamil +language ta + +intonation 2 +consonants 80 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/dra/te b/system/tts_engines/piper/engine/espeak-ng-data/lang/dra/te new file mode 100644 index 0000000..8acbb18 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/dra/te @@ -0,0 +1,7 @@ +name Telugu +language te + +status testing + +intonation 2 +//consonants 80 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/esx/kl b/system/tts_engines/piper/engine/espeak-ng-data/lang/esx/kl new file mode 100644 index 0000000..e581b58 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/esx/kl @@ -0,0 +1,3 @@ +name Greenlandic +language kl + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/eu b/system/tts_engines/piper/engine/espeak-ng-data/lang/eu new file mode 100644 index 0000000..ef132e5 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/eu @@ -0,0 +1,5 @@ +name Basque +language eu + +status testing +stressRule 15 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/gmq/da b/system/tts_engines/piper/engine/espeak-ng-data/lang/gmq/da new file mode 100644 index 0000000..58f02f1 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/gmq/da @@ -0,0 +1,4 @@ +name Danish +language da + +tunes s2 c2 q2 e2 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/gmq/is b/system/tts_engines/piper/engine/espeak-ng-data/lang/gmq/is new file mode 100644 index 0000000..04bf5ad --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/gmq/is @@ -0,0 +1,2 @@ +name Icelandic +language is diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/gmq/nb b/system/tts_engines/piper/engine/espeak-ng-data/lang/gmq/nb new file mode 100644 index 0000000..c29117f --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/gmq/nb @@ -0,0 +1,7 @@ +name Norwegian Bokmål +language nb +language no +phonemes no +dictionary no + +intonation 4 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/gmq/sv b/system/tts_engines/piper/engine/espeak-ng-data/lang/gmq/sv new file mode 100644 index 0000000..bb2d029 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/gmq/sv @@ -0,0 +1,2 @@ +name Swedish +language sv diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/af b/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/af new file mode 100644 index 0000000..64fc96f --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/af @@ -0,0 +1,8 @@ +name Afrikaans +language af + +maintainer Christo de Klerk +status mature + +roughness 0 +pitch 63 120 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/de b/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/de new file mode 100644 index 0000000..a43cc1a --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/de @@ -0,0 +1,3 @@ +name German +language de +tunes s4 c1 q4 e1 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/en b/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/en new file mode 100644 index 0000000..f23fb53 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/en @@ -0,0 +1,8 @@ +name English (Great Britain) +language en-gb 2 +language en 2 + +maintainer Reece H. Dunn +status mature + +tunes s1 c1 q1 e1 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/en-029 b/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/en-029 new file mode 100644 index 0000000..493aae4 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/en-029 @@ -0,0 +1,20 @@ +name English (Caribbean) +language en-029 +language en 10 + +maintainer Reece H. Dunn +status mature + +phonemes en-wi +dictrules 8 +stressLength 175 175 175 175 220 220 250 290 + +replace 00 D d +replace 00 T t[ +replace 00 U@ o@ +replace 03 @ a# +replace 03 3 a# +replace 03 N n + +formant 1 98 100 100 +formant 2 98 100 100 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/en-GB-scotland b/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/en-GB-scotland new file mode 100644 index 0000000..a4655a0 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/en-GB-scotland @@ -0,0 +1,17 @@ +name English (Scotland) +language en-gb-scotland +language en 4 + +maintainer Reece H. Dunn +status mature + +phonemes en-sc +dictrules 2 5 6 7 +stressLength 180 130 200 200 0 0 250 270 + +replace 03 @ V +replace 03 I i +replace 03 I2 i +replace 01 aI aI2 +replace 02 a a/ +replace 02 u: U diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/en-GB-x-gbclan b/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/en-GB-x-gbclan new file mode 100644 index 0000000..f54a06b --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/en-GB-x-gbclan @@ -0,0 +1,14 @@ +name English (Lancaster) +language en-gb-x-gbclan +language en-gb 3 +language en 5 + +maintainer Reece H. Dunn +status mature + +phonemes en-n + +stressLength 160 150 180 180 220 220 290 290 + +replace 03 N n +replace 03 i I2 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/en-GB-x-gbcwmd b/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/en-GB-x-gbcwmd new file mode 100644 index 0000000..1831f9a --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/en-GB-x-gbcwmd @@ -0,0 +1,12 @@ +name English (West Midlands) +language en-gb-x-gbcwmd +language en-gb 9 +language en 9 + +phonemes en-wm + +replace 00 h NULL +replace 00 o@ O@ +dictrules 6 +intonation 4 +stressAdd 0 0 0 0 0 0 0 20 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/en-GB-x-rp b/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/en-GB-x-rp new file mode 100644 index 0000000..fb72cf1 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/en-GB-x-rp @@ -0,0 +1,15 @@ +name English (Received Pronunciation) +language en-gb-x-rp +language en-gb 4 +language en 5 + +maintainer Reece H. Dunn +status mature + +phonemes en-rp + +replace 00 o@ O@ +replace 03 I i +replace 03 I2 i +replace 03 @ a# +replace 03 3 a# diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/en-US b/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/en-US new file mode 100644 index 0000000..31db652 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/en-US @@ -0,0 +1,15 @@ +name English (America) +language en-us 2 +language en 3 + +maintainer Reece H. Dunn +status mature + +phonemes en-us +dictrules 3 6 + +stressLength 140 120 190 170 0 0 255 300 +stressAmp 17 16 19 19 19 19 21 19 + +replace 03 I i +replace 03 I2 i diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/en-US-nyc b/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/en-US-nyc new file mode 100644 index 0000000..2d76f88 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/en-US-nyc @@ -0,0 +1,14 @@ +name English (America, New York City) +language en-us-nyc + +maintainer Richard Calvi +status testing + +phonemes en-us-nyc +dictrules 3 6 + +stressLength 140 120 190 170 0 0 255 300 +stressAmp 17 16 19 19 19 19 21 19 + +replace 03 I i +replace 03 I2 i diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/lb b/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/lb new file mode 100644 index 0000000..7972459 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/lb @@ -0,0 +1,2 @@ +name Luxembourgish +language lb diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/nl b/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/nl new file mode 100644 index 0000000..a212fbe --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/gmw/nl @@ -0,0 +1,2 @@ +name Dutch +language nl diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/grk/el b/system/tts_engines/piper/engine/espeak-ng-data/lang/grk/el new file mode 100644 index 0000000..548a01b --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/grk/el @@ -0,0 +1,2 @@ +name Greek +language el diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/grk/grc b/system/tts_engines/piper/engine/espeak-ng-data/lang/grk/grc new file mode 100644 index 0000000..baa8b2c --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/grk/grc @@ -0,0 +1,6 @@ +name Greek (Ancient) +language grc + +stressLength 170 170 190 190 0 0 230 240 +dictrules 1 +words 3 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/as b/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/as new file mode 100644 index 0000000..23991ad --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/as @@ -0,0 +1,4 @@ +name Assamese +language as + +status testing diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/bn b/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/bn new file mode 100644 index 0000000..c791802 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/bn @@ -0,0 +1,2 @@ +name Bengali +language bn diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/bpy b/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/bpy new file mode 100644 index 0000000..b63bc8d --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/bpy @@ -0,0 +1,2 @@ +name Bishnupriya Manipuri +language bpy diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/gu b/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/gu new file mode 100644 index 0000000..fcdacd2 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/gu @@ -0,0 +1,4 @@ +name Gujarati +language gu + +status testing diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/hi b/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/hi new file mode 100644 index 0000000..16ea389 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/hi @@ -0,0 +1,2 @@ +name Hindi +language hi diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/kok b/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/kok new file mode 100644 index 0000000..f2b1cc5 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/kok @@ -0,0 +1,2 @@ +name Konkani +language kok diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/mr b/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/mr new file mode 100644 index 0000000..d9181f9 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/mr @@ -0,0 +1,4 @@ +name Marathi +language mr + +status testing diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/ne b/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/ne new file mode 100644 index 0000000..4b64cc2 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/ne @@ -0,0 +1,4 @@ +name Nepali +language ne + +dictrules 1 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/or b/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/or new file mode 100644 index 0000000..95a990e --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/or @@ -0,0 +1,4 @@ +name Oriya +language or + +status testing diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/pa b/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/pa new file mode 100644 index 0000000..0e9552d --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/pa @@ -0,0 +1,2 @@ +name Punjabi +language pa diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/sd b/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/sd new file mode 100644 index 0000000..51cf8af --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/sd @@ -0,0 +1,3 @@ +name Sindhi +language sd +maintainer Ejaz Shah diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/si b/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/si new file mode 100644 index 0000000..3f87719 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/si @@ -0,0 +1,6 @@ +name Sinhala +language si + +status testing + +intonation 2 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/ur b/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/ur new file mode 100644 index 0000000..16e5469 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/inc/ur @@ -0,0 +1,7 @@ +name Urdu +language ur +maintainer Ejaz Shah +status testing + +stressRule 6 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/ine/hy b/system/tts_engines/piper/engine/espeak-ng-data/lang/ine/hy new file mode 100644 index 0000000..ee0be2f --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/ine/hy @@ -0,0 +1,3 @@ +name Armenian (East Armenia) +language hy +language hy-arevela diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/ine/hyw b/system/tts_engines/piper/engine/espeak-ng-data/lang/ine/hyw new file mode 100644 index 0000000..9d5fe86 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/ine/hyw @@ -0,0 +1,24 @@ +name Armenian (West Armenia) +language hyw +language hy-arevmda +language hy 8 + +dictionary hy +dictrules 1 + +phonemes hy + +// change consonants for West Armenian pronunciation +replace 00 b p# +replace 00 d t# +replace 00 dz ts# +replace 00 dZ tS# +replace 00 g k# + +replace 00 p b +replace 00 t d +replace 00 ts dz +replace 00 tS dZ +replace 00 k g + +replace 00 R2 R // ?? diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/ine/sq b/system/tts_engines/piper/engine/espeak-ng-data/lang/ine/sq new file mode 100644 index 0000000..9ace357 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/ine/sq @@ -0,0 +1,5 @@ +name Albanian +language sq + +// add this line to remove 'ë' at the end of words +// replace 00 @/ NULL diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/ira/fa b/system/tts_engines/piper/engine/espeak-ng-data/lang/ira/fa new file mode 100644 index 0000000..5a0c2b6 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/ira/fa @@ -0,0 +1,4 @@ +name Persian +language fa +maintainer Shadyar Khodayari +status mature diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/ira/fa-Latn b/system/tts_engines/piper/engine/espeak-ng-data/lang/ira/fa-Latn new file mode 100644 index 0000000..02c6da6 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/ira/fa-Latn @@ -0,0 +1,8 @@ +name Persian (Pinglish) +// Sometimes, Farsi speakers write Farsi words using English characters, particularly in Chat and SMS (texte messages).), called Pinglish +language fa-latn +maintainer Shadyar Khodayari +status mature +dictrules 1 +phonemes fa + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/ira/ku b/system/tts_engines/piper/engine/espeak-ng-data/lang/ira/ku new file mode 100644 index 0000000..b26bca2 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/ira/ku @@ -0,0 +1,5 @@ +name Kurdish +language ku + +//words 1 48 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/iro/chr b/system/tts_engines/piper/engine/espeak-ng-data/lang/iro/chr new file mode 100644 index 0000000..abe2938 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/iro/chr @@ -0,0 +1,27 @@ +name Cherokee //https://github.com/espeak-ng/espeak-ng/blob/master/docs/voices.md +language chr-US-Qaaa-x-west 5 + +maintainer Michael Conrad +status testing + +pitch 90 160 + +voicing 100 + +consonants 100 100 + +speed 100 + +words 2 1 + +phonemes chr + +//stress on all syllables to simulate stress on no syllables +stressRule 9 +stressLength 175 175 175 175 175 175 175 175 //all vowels the same length regardless of stress +stressAmp 10 10 10 10 10 10 10 10 //all vowels the same strength regardless of marked stress + +intonation 1 + +tunes chrs chrc chrq chre + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/itc/la b/system/tts_engines/piper/engine/espeak-ng-data/lang/itc/la new file mode 100644 index 0000000..c57270f --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/itc/la @@ -0,0 +1,12 @@ +name Latin +language la +stressRule 2 0 2 +// rule=penultimate +// unstressed_wd1=0 +// unstressed_wd2=2 +stressOpt 0 5 // flags=0100001 (no automatic secondary stress + don't stres monosyllables) + +// short gap between words +words 2 + +// Note: The Latin voice needs long vowels to be marked with macrons diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/jpx/ja b/system/tts_engines/piper/engine/espeak-ng-data/lang/jpx/ja new file mode 100644 index 0000000..20e21a1 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/jpx/ja @@ -0,0 +1,5 @@ +name Japanese +language ja +phonemes ja + +intonation 4 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/ko b/system/tts_engines/piper/engine/espeak-ng-data/lang/ko new file mode 100644 index 0000000..f03ba09 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/ko @@ -0,0 +1,5 @@ +name Korean +language ko +pitch 80 118 +intonation 2 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/map/haw b/system/tts_engines/piper/engine/espeak-ng-data/lang/map/haw new file mode 100644 index 0000000..c086913 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/map/haw @@ -0,0 +1,3 @@ +name Hawaiian +language haw +status testing diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/miz/mto b/system/tts_engines/piper/engine/espeak-ng-data/lang/miz/mto new file mode 100644 index 0000000..eb0f75f --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/miz/mto @@ -0,0 +1,8 @@ +name Totontepec Mixe +language mto + +maintainer Bill Dengler and Elizabeth Resendiz +status testing + +lowercaseSentence +tunes s6 c6 q6 e6 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/myn/quc b/system/tts_engines/piper/engine/espeak-ng-data/lang/myn/quc new file mode 100644 index 0000000..e8e0ec6 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/myn/quc @@ -0,0 +1,6 @@ +name K'iche' +language quc +status testing +stressRule 3 // stress on final syllable +stressAmp 8 8 20 15 0 0 25 25 // reduce unstressed vowels +stressLength 120 120 200 150 0 0 250 250 // reduce unstressed vowels diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/poz/id b/system/tts_engines/piper/engine/espeak-ng-data/lang/poz/id new file mode 100644 index 0000000..d300372 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/poz/id @@ -0,0 +1,7 @@ +name Indonesian +language id + +stressLength 160 200 180 180 0 0 220 240 +stressAmp 16 18 18 18 0 0 22 21 + +consonants 80 80 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/poz/mi b/system/tts_engines/piper/engine/espeak-ng-data/lang/poz/mi new file mode 100644 index 0000000..fa1c71e --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/poz/mi @@ -0,0 +1,21 @@ +name Māori +language mi +status testing + +// https://github.com/espeak-ng/espeak-ng/blob/master/docs/voices.md#words +words 1 2 + +// taken from Jacky +pitch 115 130 + +formant 0 150 155 100 +formant 1 90 155 70 +formant 2 95 70 64 +formant 3 15 20 30 +formant 4 20 30 40 +formant 5 65 20 65 +formant 6 70 80 100 +formant 7 20 80 100 +formant 8 100 95 80 +voicing 135 +consonants 110 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/poz/ms b/system/tts_engines/piper/engine/espeak-ng-data/lang/poz/ms new file mode 100644 index 0000000..27a2712 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/poz/ms @@ -0,0 +1,14 @@ +// Last updated: 14 October 2010, Jason Ong (jason@portalgroove.com) +name Malay +language ms +phonemes id + +stressLength 160 200 180 180 0 0 220 240 +stressAmp 16 18 18 18 0 0 22 21 +intonation 3 // Less intonation, and comma does not raise the pitch. + +// Nuance - Peninsula Malaysia +// replace 3 a @ // change 'saya' to 'saye' + // (only the last phoneme of a word, only in unstressed syllables) + +consonants 80 80 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/qu b/system/tts_engines/piper/engine/espeak-ng-data/lang/qu new file mode 100644 index 0000000..aa717f2 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/qu @@ -0,0 +1,5 @@ +name Quechua +language qu +stressRule 2 // stress on penultimate syllable +status testing + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/an b/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/an new file mode 100644 index 0000000..dc75aa8 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/an @@ -0,0 +1,2 @@ +name Aragonese +language an diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/ca b/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/ca new file mode 100644 index 0000000..54af356 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/ca @@ -0,0 +1,2 @@ +name Catalan +language ca diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/es b/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/es new file mode 100644 index 0000000..dc90e41 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/es @@ -0,0 +1,4 @@ +name Spanish (Spain) +language es +dictrules 1 +tunes s6 c6 q6 e6 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/es-419 b/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/es-419 new file mode 100644 index 0000000..236bef0 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/es-419 @@ -0,0 +1,11 @@ +name Spanish (Latin America) +language es-419 +language es-mx 6 + +phonemes es-la +dictrules 2 +intonation 2 +stressLength 170 200 230 180 0 0 250 280 + +tunes s6 c6 q6 e6 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/fr b/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/fr new file mode 100644 index 0000000..9e0b944 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/fr @@ -0,0 +1,6 @@ +name French (France) +language fr-fr +language fr + +dictrules 1 +tunes s3 c3 q3 e3 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/fr-BE b/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/fr-BE new file mode 100644 index 0000000..1a0311f --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/fr-BE @@ -0,0 +1,8 @@ +name French (Belgium) +language fr-be +language fr 8 + +dictrules 2 +tunes s3 c3 q3 e3 + + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/fr-CH b/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/fr-CH new file mode 100644 index 0000000..2e45fbd --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/fr-CH @@ -0,0 +1,6 @@ +name French (Switzerland) +language fr-ch +language fr 8 + +dictrules 3 +tunes s3 c3 q3 e3 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/ht b/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/ht new file mode 100644 index 0000000..8b57225 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/ht @@ -0,0 +1,8 @@ +name Haitian Creole +language ht +status testing +maintainer // TODO somebody should take responsibility for this + +phonemes ht +dictionary ht + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/it b/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/it new file mode 100644 index 0000000..ad7bb50 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/it @@ -0,0 +1,7 @@ +name Italian +language it + +maintainer Christian Leo M +status mature + +tunes s4 c4 q4 e4 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/pap b/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/pap new file mode 100644 index 0000000..b9e07fe --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/pap @@ -0,0 +1,7 @@ +name Papiamento +language pap + +status testing + +phonemes base2 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/pt b/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/pt new file mode 100644 index 0000000..5041b51 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/pt @@ -0,0 +1,7 @@ +name Portuguese (Portugal) +language pt +language pt-pt +phonemes pt-pt + +dictrules 1 +intonation 2 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/pt-BR b/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/pt-BR new file mode 100644 index 0000000..52b850c --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/pt-BR @@ -0,0 +1,7 @@ +name Portuguese (Brazil) +language pt-br +language pt 6 + +dictrules 2 +stressLength 200 115 230 230 0 0 250 270 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/ro b/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/ro new file mode 100644 index 0000000..94441ef --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/roa/ro @@ -0,0 +1,2 @@ +name Romanian +language ro diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/sai/gn b/system/tts_engines/piper/engine/espeak-ng-data/lang/sai/gn new file mode 100644 index 0000000..6e183e5 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/sai/gn @@ -0,0 +1,4 @@ +name Guarani +language gn +dictrules 1 +words 0 1 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/sem/am b/system/tts_engines/piper/engine/espeak-ng-data/lang/sem/am new file mode 100644 index 0000000..87ed65a --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/sem/am @@ -0,0 +1,4 @@ +name Amharic +language am + +status testing diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/sem/ar b/system/tts_engines/piper/engine/espeak-ng-data/lang/sem/ar new file mode 100644 index 0000000..5219808 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/sem/ar @@ -0,0 +1,5 @@ +name Arabic +language ar +phonemes ar + +stressRule 4 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/sem/he b/system/tts_engines/piper/engine/espeak-ng-data/lang/sem/he new file mode 100644 index 0000000..38cce90 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/sem/he @@ -0,0 +1,4 @@ +name Hebrew +language he + +status testing diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/sem/mt b/system/tts_engines/piper/engine/espeak-ng-data/lang/sem/mt new file mode 100644 index 0000000..ea8a3fc --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/sem/mt @@ -0,0 +1,4 @@ +name Maltese +language mt + +status testing diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/sit/cmn b/system/tts_engines/piper/engine/espeak-ng-data/lang/sit/cmn new file mode 100644 index 0000000..451016d --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/sit/cmn @@ -0,0 +1,36 @@ +name Chinese (Mandarin, latin as English) +language cmn +language zh-cmn +language zh + +phonemes cmn +dictionary cmn +words 1 +pitch 80 118 + +dict_min 100000 + +//for some dialects + +//[en]: replace ng with n +//[zh]: �޺�������ng���n +//replace 0 N n + +//[en]: replace rfx consonants +//[zh]: �޾�������r���l��z��er���e +//replace 0 ts.h tsh +//replace 0 ts. ts +//replace 0 s. s +//replace 0 i. i[ +//replace 0 z. l +//replace 0 z. z +//replace 0 @r @ + +//[en]: replace beginning n or l +//[zh]: ����nl��n���l��l���n +//replace 2 n l +//replace 2 l n + +//[en]: replace beginning w with v +//[zh]: w���v +//replace 0 w v diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/sit/cmn-Latn-pinyin b/system/tts_engines/piper/engine/espeak-ng-data/lang/sit/cmn-Latn-pinyin new file mode 100644 index 0000000..4b63db7 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/sit/cmn-Latn-pinyin @@ -0,0 +1,11 @@ +name Chinese (Mandarin, latin as Pinyin) +language cmn-latn-pinyin +language zh-cmn +language zh + +phonemes cmn +dictionary cmn +words 1 +pitch 80 118 + +dict_min 100000 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/sit/hak b/system/tts_engines/piper/engine/espeak-ng-data/lang/sit/hak new file mode 100644 index 0000000..0c3bf14 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/sit/hak @@ -0,0 +1,6 @@ +name Hakka Chinese +language hak +maintainer Chen Chien-ting +status testing +phonemes hak +dictionary hak diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/sit/my b/system/tts_engines/piper/engine/espeak-ng-data/lang/sit/my new file mode 100644 index 0000000..3dbc767 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/sit/my @@ -0,0 +1,3 @@ +name Myanmar (Burmese) +maintainer Min Maung +language my diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/sit/yue b/system/tts_engines/piper/engine/espeak-ng-data/lang/sit/yue new file mode 100644 index 0000000..d65fac1 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/sit/yue @@ -0,0 +1,13 @@ +name Chinese (Cantonese) +language yue +language zh-yue +language zh 8 + +phonemes yue +dictionary yue + +// interpret English letters as 1=English words, 2=jyutping +dictrules 1 + +words 1 +dict_min 10000 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/sit/yue-Latn-jyutping b/system/tts_engines/piper/engine/espeak-ng-data/lang/sit/yue-Latn-jyutping new file mode 100644 index 0000000..2324cd7 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/sit/yue-Latn-jyutping @@ -0,0 +1,13 @@ +name Chinese (Cantonese, latin as Jyutping) +language yue +language zh-yue +language zh 8 + +phonemes yue +dictionary yue + +// interpret English letters as 1=English words, 2=jyutping +dictrules 2 + +words 1 +dict_min 10000 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/tai/shn b/system/tts_engines/piper/engine/espeak-ng-data/lang/tai/shn new file mode 100644 index 0000000..8d30384 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/tai/shn @@ -0,0 +1,4 @@ +name Shan (Tai Yai) +language shn +maintainer ronaldaug +status testing diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/tai/th b/system/tts_engines/piper/engine/espeak-ng-data/lang/tai/th new file mode 100644 index 0000000..9b337c1 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/tai/th @@ -0,0 +1,3 @@ +name Thai +language th +status testing diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/az b/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/az new file mode 100644 index 0000000..fd9e4f3 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/az @@ -0,0 +1,4 @@ +name Azerbaijani +language az + +status testing diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/ba b/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/ba new file mode 100644 index 0000000..09fab48 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/ba @@ -0,0 +1,2 @@ +name Bashkir +language ba diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/cv b/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/cv new file mode 100644 index 0000000..44e37f6 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/cv @@ -0,0 +1,3 @@ +name Chuvash +language cv +status testing diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/kk b/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/kk new file mode 100644 index 0000000..df3b0a0 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/kk @@ -0,0 +1,4 @@ +name Kazakh +language kk +status testing + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/ky b/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/ky new file mode 100644 index 0000000..c031449 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/ky @@ -0,0 +1,4 @@ +name Kyrgyz +language ky + +tunes s3 c3 q3 e3 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/nog b/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/nog new file mode 100644 index 0000000..691d031 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/nog @@ -0,0 +1,3 @@ +name Nogai +language nog +status testing diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/tk b/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/tk new file mode 100644 index 0000000..795413a --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/tk @@ -0,0 +1,2 @@ +name Turkmen +language tk diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/tr b/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/tr new file mode 100644 index 0000000..1d5cbc4 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/tr @@ -0,0 +1,2 @@ +name Turkish +language tr diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/tt b/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/tt new file mode 100644 index 0000000..10ebba4 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/tt @@ -0,0 +1,2 @@ +name Tatar +language tt diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/ug b/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/ug new file mode 100644 index 0000000..f81b44f --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/ug @@ -0,0 +1,2 @@ +name Uyghur +language ug diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/uz b/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/uz new file mode 100644 index 0000000..b1a7586 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/trk/uz @@ -0,0 +1,4 @@ +name Uzbek +language uz + +status testing diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/urj/et b/system/tts_engines/piper/engine/espeak-ng-data/lang/urj/et new file mode 100644 index 0000000..096565f --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/urj/et @@ -0,0 +1,10 @@ +name Estonian +language et + +stressAmp 18 16 22 22 20 22 22 22 +stressLength 150 180 200 200 0 0 210 250 +stressOpt 1 2 4 6 // (S_NO_DIM + S_FINAL_DIM = S_FINAL_DIM_ONLY), S_FINAL_NO_2, S_2_TO_HEAVY +stressRule 0 + +intonation 3 +spellingStress diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/urj/fi b/system/tts_engines/piper/engine/espeak-ng-data/lang/urj/fi new file mode 100644 index 0000000..fe2447c --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/urj/fi @@ -0,0 +1,11 @@ +name Finnish +language fi + + +stressAmp 18 16 22 22 20 22 22 22 +stressLength 150 180 200 200 0 0 210 250 +stressOpt 1 2 4 6 // (S_NO_DIM + S_FINAL_DIM = S_FINAL_DIM_ONLY), S_FINAL_NO_2, S_2_TO_HEAVY +stressRule 0 + +intonation 3 +spellingStress diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/urj/hu b/system/tts_engines/piper/engine/espeak-ng-data/lang/urj/hu new file mode 100644 index 0000000..f5bb295 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/urj/hu @@ -0,0 +1,7 @@ +name Hungarian +language hu +brackets 0 +bracketsAnnounced 0 +pitch 81 117 + + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/urj/smj b/system/tts_engines/piper/engine/espeak-ng-data/lang/urj/smj new file mode 100644 index 0000000..316559c --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/urj/smj @@ -0,0 +1,4 @@ +name Lule Saami +language smj + +status testing diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/zle/be b/system/tts_engines/piper/engine/espeak-ng-data/lang/zle/be new file mode 100644 index 0000000..31b14e5 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/zle/be @@ -0,0 +1,4 @@ +name Belarusian +language be +dict_min 2000 +speed 95 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/zle/ru b/system/tts_engines/piper/engine/espeak-ng-data/lang/zle/ru new file mode 100644 index 0000000..959754b --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/zle/ru @@ -0,0 +1,4 @@ +name Russian +language ru +replace 03 a a# +dict_min 20000 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/zle/ru-LV b/system/tts_engines/piper/engine/espeak-ng-data/lang/zle/ru-LV new file mode 100644 index 0000000..fc9541b --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/zle/ru-LV @@ -0,0 +1,16 @@ +name Russian (Latvia) +language ru-lv 2 + +maintainer Valdis Vitolins +status testing + +phonemes ru-lv +dictrules 2 +dict_min 20000 +speed 95 + +words 0 2 +tone 150 220 450 255 750 20 3500 255 +stressAmp 12 10 8 8 0 0 16 17 +stressLength 160 140 200 140 0 0 240 160 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/zle/ru-cl b/system/tts_engines/piper/engine/espeak-ng-data/lang/zle/ru-cl new file mode 100644 index 0000000..ad100ca --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/zle/ru-cl @@ -0,0 +1,6 @@ +name Russian (Classic) +language ru-cl +replace 03 a a# +dict_min 20000 +speed 95 +dictrules 3 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/zle/uk b/system/tts_engines/piper/engine/espeak-ng-data/lang/zle/uk new file mode 100644 index 0000000..b318c23 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/zle/uk @@ -0,0 +1,7 @@ +name Ukrainian +language uk + +maintainer Andrij Mizyk +status testing + +speed 80 \ No newline at end of file diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/zls/bg b/system/tts_engines/piper/engine/espeak-ng-data/lang/zls/bg new file mode 100644 index 0000000..870c042 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/zls/bg @@ -0,0 +1,5 @@ +name Bulgarian +language bg + +stressAmp 13 12 17 17 20 22 22 21 +stressLength 180 170 200 200 200 200 210 220 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/zls/bs b/system/tts_engines/piper/engine/espeak-ng-data/lang/zls/bs new file mode 100644 index 0000000..01fd174 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/zls/bs @@ -0,0 +1,14 @@ +name Bosnian +language bs +phonemes hr + +pitch 81 120 +formant 0 100 100 100 +formant 1 97 97 100 +formant 2 97 97 100 +formant 3 97 102 100 +formant 4 97 102 100 +formant 5 97 102 100 + +stressAdd 10 10 0 0 0 0 -30 -30 +dictrules 3 4 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/zls/hr b/system/tts_engines/piper/engine/espeak-ng-data/lang/zls/hr new file mode 100644 index 0000000..d1cae3a --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/zls/hr @@ -0,0 +1,15 @@ +name Croatian +language hr +language hbs + +// attributes towards !variant3 +pitch 81 120 +formant 0 100 100 100 +formant 1 97 97 100 +formant 2 97 97 100 +formant 3 97 102 100 +formant 4 97 102 100 +formant 5 97 102 100 + +stressAdd 10 10 0 0 0 0 -30 -30 +dictrules 1 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/zls/mk b/system/tts_engines/piper/engine/espeak-ng-data/lang/zls/mk new file mode 100644 index 0000000..380c20b --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/zls/mk @@ -0,0 +1,2 @@ +name Macedonian +language mk diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/zls/sl b/system/tts_engines/piper/engine/espeak-ng-data/lang/zls/sl new file mode 100644 index 0000000..b7bf01f --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/zls/sl @@ -0,0 +1,4 @@ +name Slovenian +language sl + +status testing diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/zls/sr b/system/tts_engines/piper/engine/espeak-ng-data/lang/zls/sr new file mode 100644 index 0000000..81e3390 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/zls/sr @@ -0,0 +1,13 @@ +name Serbian +language sr + +// attributes towards !variant3 pitch 80 120 +formant 0 100 100 100 +formant 1 97 97 100 +formant 2 97 97 100 +formant 3 97 102 100 +formant 4 97 102 100 +formant 5 97 102 100 + +stressAdd 10 10 0 0 0 0 -30 -30 +dictrules 2 4 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/zlw/cs b/system/tts_engines/piper/engine/espeak-ng-data/lang/zlw/cs new file mode 100644 index 0000000..5c3bf2a --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/zlw/cs @@ -0,0 +1,2 @@ +name Czech +language cs diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/zlw/pl b/system/tts_engines/piper/engine/espeak-ng-data/lang/zlw/pl new file mode 100644 index 0000000..b85f56b --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/zlw/pl @@ -0,0 +1,4 @@ +name Polish +language pl + +intonation 2 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lang/zlw/sk b/system/tts_engines/piper/engine/espeak-ng-data/lang/zlw/sk new file mode 100644 index 0000000..e849e61 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/lang/zlw/sk @@ -0,0 +1,2 @@ +name Slovak +language sk diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lb_dict b/system/tts_engines/piper/engine/espeak-ng-data/lb_dict new file mode 100644 index 0000000..d0aee67 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/lb_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lfn_dict b/system/tts_engines/piper/engine/espeak-ng-data/lfn_dict new file mode 100644 index 0000000..9a8d4f8 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/lfn_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lt_dict b/system/tts_engines/piper/engine/espeak-ng-data/lt_dict new file mode 100644 index 0000000..d0dd29f Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/lt_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/lv_dict b/system/tts_engines/piper/engine/espeak-ng-data/lv_dict new file mode 100644 index 0000000..60d70b1 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/lv_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/mi_dict b/system/tts_engines/piper/engine/espeak-ng-data/mi_dict new file mode 100644 index 0000000..b727a30 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/mi_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/mk_dict b/system/tts_engines/piper/engine/espeak-ng-data/mk_dict new file mode 100644 index 0000000..974626c Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/mk_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/ml_dict b/system/tts_engines/piper/engine/espeak-ng-data/ml_dict new file mode 100644 index 0000000..e12f222 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/ml_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/mr_dict b/system/tts_engines/piper/engine/espeak-ng-data/mr_dict new file mode 100644 index 0000000..2cbf14d Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/mr_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/ms_dict b/system/tts_engines/piper/engine/espeak-ng-data/ms_dict new file mode 100644 index 0000000..cb6fc29 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/ms_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/mt_dict b/system/tts_engines/piper/engine/espeak-ng-data/mt_dict new file mode 100644 index 0000000..8cc965b Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/mt_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/mto_dict b/system/tts_engines/piper/engine/espeak-ng-data/mto_dict new file mode 100644 index 0000000..280aaa4 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/mto_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/my_dict b/system/tts_engines/piper/engine/espeak-ng-data/my_dict new file mode 100644 index 0000000..ca129b4 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/my_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/nci_dict b/system/tts_engines/piper/engine/espeak-ng-data/nci_dict new file mode 100644 index 0000000..4f001af Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/nci_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/ne_dict b/system/tts_engines/piper/engine/espeak-ng-data/ne_dict new file mode 100644 index 0000000..fa8e29c Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/ne_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/nl_dict b/system/tts_engines/piper/engine/espeak-ng-data/nl_dict new file mode 100644 index 0000000..4b371b1 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/nl_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/no_dict b/system/tts_engines/piper/engine/espeak-ng-data/no_dict new file mode 100644 index 0000000..e1805fa Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/no_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/nog_dict b/system/tts_engines/piper/engine/espeak-ng-data/nog_dict new file mode 100644 index 0000000..0819a51 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/nog_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/om_dict b/system/tts_engines/piper/engine/espeak-ng-data/om_dict new file mode 100644 index 0000000..81fa80d Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/om_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/or_dict b/system/tts_engines/piper/engine/espeak-ng-data/or_dict new file mode 100644 index 0000000..ad2d191 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/or_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/pa_dict b/system/tts_engines/piper/engine/espeak-ng-data/pa_dict new file mode 100644 index 0000000..08485ee Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/pa_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/pap_dict b/system/tts_engines/piper/engine/espeak-ng-data/pap_dict new file mode 100644 index 0000000..3fccee6 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/pap_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/phondata b/system/tts_engines/piper/engine/espeak-ng-data/phondata new file mode 100644 index 0000000..5b3c94a Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/phondata differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/phondata-manifest b/system/tts_engines/piper/engine/espeak-ng-data/phondata-manifest new file mode 100644 index 0000000..e7eac9a --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/phondata-manifest @@ -0,0 +1,987 @@ +# This file lists the type of data that has been compiled into the +# phondata file +# +# The first character of a line indicates the type of data: +# S - A SPECT_SEQ structure +# W - A wavefile segment +# E - An envelope +# +# Address is the displacement within phondata of this item +# +# Address Data file +# ------- --------- +W 0x00008 ustop/null +S 0x00158 vowel/@ +S 0x0021c vowel/@- +S 0x002e0 vowel/a +S 0x003e4 vowel/e +S 0x004e8 vowel/i +S 0x005ec vowel/oo +S 0x006f0 vowel/u +S 0x007f4 klatt/m-syl +S 0x008f8 m/m-syl +S 0x009bc klatt/n-syl +S 0x00ac0 n/n-syl +S 0x00b84 nn/nn-syl +W 0x00cc8 ustop/percus10 +S 0x00ed4 vowelr/r-voc +S 0x01018 vwl_hi/l-voc +S 0x0111c r/r@ +S 0x011e0 r/ra +S 0x012a4 r/re +S 0x01368 r/ri +S 0x0142c r/ro +S 0x014f0 r/ru +S 0x015b4 r/xr +S 0x01638 r/_r +S 0x016fc r/tr +S 0x01780 r/r +S 0x01844 r3/r_n +W 0x018c8 r3/rx +S 0x02c20 r/trr +S 0x02ce4 r/rr +S 0x02da8 r3/r_ +S 0x02e2c r3/r_trill2 +W 0x02ef0 r3/r_trill2.wav +S 0x035fc r3/r_trill +W 0x03700 r3/r_trill.wav +W 0x0416c r3/r_trill3.wav +S 0x045b0 r3/r_uvl +W 0x046b4 r3/r_uvl.wav +S 0x055bc l/l@ +S 0x05680 l/la +S 0x05704 l/le +S 0x05788 l/li +S 0x0584c l/lo +S 0x05950 l/lu +S 0x05a14 l/L1_@L +S 0x05b18 l/L1_aL +S 0x05c1c l/L1_eL +S 0x05ce0 l/L1_iL +S 0x05de4 l/L1_oL +S 0x05f28 l/L1_uL +S 0x0602c l/l_ +S 0x060b0 l/xl +S 0x06134 l/_l +S 0x061f8 l/tl +S 0x0627c l/l_long +S 0x06300 l/l +S 0x06384 l/L2_eL +S 0x06448 l/L2_uL +S 0x0654c l/L2_@L +S 0x06650 l/L2_aL +S 0x06754 l/L2_iL +S 0x06858 l/L2_oL +S 0x0699c l/l_@ +S 0x06a20 l/l_a +S 0x06aa4 l/l_e +S 0x06b28 l/l_i +S 0x06bec l/l_o +S 0x06cb0 l/l_u +S 0x06d34 l^/j2@ +S 0x06df8 l^/j2a +S 0x06ebc l^/j2e +S 0x06f80 l^/j2i +S 0x07044 l^/j2o +S 0x07148 l^/j2u +S 0x0724c l^/_l^ +S 0x07350 l^/l^ +S 0x07454 l^/l_rfx +S 0x07518 ll/xll +S 0x075dc ll/_ll +S 0x076e0 ll/ll +S 0x077a4 w/w@ +S 0x07868 w/wa +S 0x0792c w/we +S 0x079f0 w/wi +S 0x07ab4 w/wo +S 0x07b78 w/wu +S 0x07c3c w/xw +S 0x07cc0 w/w +S 0x07d44 w/_w +S 0x07e08 w/iw_ +S 0x07f0c w/w_ +S 0x07fd0 j/j@ +S 0x080d4 j/ja +S 0x081d8 j/je +S 0x082dc j/ji +S 0x083a0 j/jo +S 0x084e4 j/ju +S 0x085a8 j/xj +S 0x0862c j/_j +S 0x086b0 j/j_ +S 0x08774 j2/j2@ +S 0x08838 j2/j2a +S 0x088fc j2/j2e +S 0x089c0 j2/j2i +S 0x08a84 j2/j2o +S 0x08b88 j2/j2u +S 0x08c4c j2/xj2 +S 0x08cd0 j2/_j2 +S 0x08d54 klatt/m_ +S 0x08e58 klatt/m +S 0x08f5c m/m@ +S 0x09060 m/ma +S 0x09164 m/me +S 0x09268 m/mi +S 0x093ac m/mo +S 0x094b0 m/mu +S 0x095b4 m/mj +S 0x09678 m/_m +S 0x096fc m/m_ +S 0x097c0 klatt/n +S 0x098c4 n/n@ +S 0x099c8 n/na +S 0x09acc n/ne +S 0x09bd0 n/ni +S 0x09cd4 n/no +S 0x09dd8 n/nu +S 0x09edc n/nj +S 0x09fa0 n/_n +S 0x0a024 n/n_ +S 0x0a0e8 klatt/nr +S 0x0a1ec n/nr@ +S 0x0a2f0 n/nra +S 0x0a3f4 n/nre +S 0x0a4f8 n/nri +S 0x0a5fc n/nro +S 0x0a700 n/nru +S 0x0a804 n/_nr +S 0x0a888 n/nr_ +S 0x0a94c klatt/n^@ +S 0x0a9d0 klatt/n^ +S 0x0aad4 n^/n^@ +S 0x0ac58 n^/n^a +S 0x0ad9c n^/n^e +S 0x0af20 n^/n^i +S 0x0b0a4 n^/n^o +S 0x0b268 n^/n^u +S 0x0b3ec n^/_n^ +S 0x0b470 n^/n^_ +S 0x0b5b4 klatt/nn_ +S 0x0b6b8 klatt/nn +S 0x0b7bc nn/nn@ +S 0x0b880 nn/nna +S 0x0b944 nn/nne +S 0x0ba08 nn/nni +S 0x0bacc nn/nno +S 0x0bb90 nn/nnu +S 0x0bc54 nn/inn +S 0x0bd18 nn/nnj +S 0x0bd9c nn/_nn +S 0x0be20 nn/nn_ +S 0x0bee4 r3/@tap2 +S 0x0c028 r3/@tap +S 0x0c16c d/tap1 +S 0x0c230 d/tap3 +S 0x0c334 d/dr +S 0x0c3b8 r3/@tap_rfx_ +S 0x0c4fc r3/@tap_rfx +S 0x0c640 b/xb +S 0x0c704 klatt/b +W 0x0c7c8 x/b_ +W 0x0cbe0 x/b +S 0x0cd44 b/b_ +S 0x0ce08 b/b@2 +S 0x0cf0c b/b@ +S 0x0d010 b/ba +S 0x0d114 b/be +S 0x0d218 b/bi +S 0x0d31c b/bo +S 0x0d420 b/bu +S 0x0d524 b/b +S 0x0d628 d/xd +S 0x0d6ec d/d_ +W 0x0d7b0 x/d_ +S 0x0dbd8 d/d +W 0x0dc9c x/d +W 0x0deb8 x/d_dnt +S 0x0e19c dzh/xdzh +S 0x0e260 dzh/dzh_ +W 0x0e324 x/dzh_ +S 0x0ed5c dzh/dzh +W 0x0ee20 x/dzh +W 0x0f22c x/dz_pzd +S 0x0f68c dzh/xdz_pzd +S 0x0f750 klatt/dz_pzd_ +S 0x0f814 klatt/dz_pzd +S 0x0f8d8 dzh/dz_pzd_ +S 0x0f99c dzh/dz_pzd +S 0x0fa60 g/xg +S 0x0fb24 g/g_ +W 0x0fbe8 x/g_ +S 0x0ffa8 g/g +W 0x1006c x/g2 +S 0x102f4 klatt/v_ +W 0x103b8 vocw/v +S 0x10cb8 klatt/bh +S 0x10d7c voc/v_ +S 0x10e40 voc/bh +S 0x10f04 klatt/v +S 0x10fc8 voc/v +S 0x110cc voc/v#_ +S 0x11190 voc/v# +S 0x11294 voc/dh_ +W 0x11358 vocw/dh +S 0x11c60 voc/dh +S 0x11d24 voc/z_ +W 0x11de8 ufric/s_ +S 0x1289c voc/z +S 0x12960 klatt/zh_ +W 0x12a24 vocw/zh +S 0x1338c klatt/zh +S 0x13450 voc/zh_ +S 0x13514 voc/zh +W 0x135d8 vocw/zh_rfx +S 0x13e68 voc/z_pzd_ +W 0x13f2c ufric/s_pzd_ +S 0x14a30 voc/z_pzd +W 0x14af4 ufric/s_pzd +W 0x1542c ufric/sh_pzd_ +W 0x15ef4 ufric/sh_pzd +S 0x16828 voc/j +W 0x1692c ufric/ch +S 0x17044 klatt/qqh_ +W 0x17108 vocw/Q_ +S 0x17914 klatt/qqh +W 0x179d8 vocw/Q +S 0x181e4 voc/Q_ +S 0x182a8 voc/Q +S 0x1836c voc/Q_ulv +W 0x18470 ufric/xx +W 0x18f6c ustop/p_ +W 0x1977c ustop/pr +W 0x19bc4 ustop/p_unasp +W 0x19ef0 ustop/pl +W 0x1a2e8 ustop/p +W 0x1a658 ustop/t_ +W 0x1ab18 ustop/tr +W 0x1b278 ustop/t_dnt +W 0x1b688 ustop/t +W 0x1babc ustop/t_hi +W 0x1bd88 ustop/tsh_ +W 0x1c6c8 ustop/tsh +W 0x1cd08 ustop/ts_pzd +W 0x1d40c ustop/t_pzd +W 0x1d858 ustop/c +W 0x1db08 ustop/k_ +W 0x1dfc4 ustop/kr +W 0x1e604 ustop/ki +W 0x1ebcc ustop/kl +W 0x1f1fc ustop/k_unasp +W 0x1f6d0 ustop/k +W 0x1fba4 ustop/q +W 0x1fd10 ustop/q_u +W 0x1fe30 ufric/f_ +W 0x20900 ufric/f +W 0x210f0 ufric/th_ +W 0x21970 ufric/th +W 0x22208 ufric/s! +W 0x22ab8 ufric/s +W 0x23258 ufric/sh_ +W 0x23d08 ufric/sh +W 0x246b8 ufric/sh_rfx +W 0x25054 ufric/l# +W 0x25a98 ufric/ch_ +W 0x263c0 ufric/x_ +W 0x26f1c ufric/x_hr +W 0x27830 h/h@ +W 0x27d84 h/ha +W 0x28400 h/he +W 0x28a94 h/hi +W 0x2902c h/ho +W 0x296e8 h/hu +W 0x29e0c h/h_ +W 0x2a4b8 ustop/ts_ +W 0x2ae18 ustop/ts +S 0x2b874 d/xdz +W 0x2b938 ustop/p_unasp_ +W 0x2ba74 ustop/p_asp +W 0x2bf78 ustop/t_short +W 0x2c1bc ustop/ts_pzd_ +W 0x2c680 ustop/ts_pzd2 +W 0x2c9b4 ustop/k_asp +W 0x2d0b8 ustop/k_asp_e +W 0x2d820 ustop/k_asp_a +W 0x2e0a4 ufric/s_continue +S 0x2e854 vowel/a# +S 0x2e958 vowel/a_2 +S 0x2ea5c vowel/ee_1 +S 0x2eba0 vowel/o +S 0x2ece4 vowel/oo_4 +S 0x2ede8 vowel/u_bck +S 0x2eeec vowel/uu_2 +S 0x2eff0 vowel/y +S 0x2f134 vowel/y# +S 0x2f238 vdiph/au_4 +S 0x2f3bc vdiph/eu +S 0x2f500 vdiph2/iu +S 0x2f684 vdiph/ai +S 0x2f7c8 vdiph/ei +S 0x2f90c vdiph/eei +S 0x2fa90 vdiph/oi +S 0x2fc54 vdiph/ui +S 0x2fd98 w/w2 +W 0x2fe5c ustop/t_dnt2 +S 0x300b4 vowel/ii_5 +S 0x301b8 vowel/e_mid2 +S 0x302bc vowel/ii#_2 +S 0x303c0 vdiph/ai_5 +S 0x30504 vowel/a_6 +S 0x30608 vdiph/@i_2 +S 0x3074c vowel/@_3 +S 0x30810 vowel/o-_4 +S 0x30914 vowel/u_2 +W 0x30a18 ustop/tsh_unasp +W 0x30ed8 ustop/k_ejc +W 0x31688 vwl_ar/hah +W 0x325ec vwl_ar/dhad +W 0x3364c vwl_ar/shin +S 0x34428 vowel/aa_7 +S 0x3452c vowel/u_7 +S 0x34630 vowel/ii# +S 0x34734 vowel/oe +S 0x34838 vowel/o_mid +S 0x3493c vowel/@_2 +S 0x34a40 vowel/ee +S 0x34b44 vowel/aa_8 +S 0x34c48 vowel/0_3 +S 0x34d4c vowel/e_6 +S 0x34e50 vowel/ii +E 0x34f54 envelope/i_risefall +E 0x34fd4 envelope/p_fall +E 0x35054 envelope/p_214 +E 0x350d4 envelope/p_rise +E 0x35154 envelope/p_level +S 0x351d4 n/n_long_ +S 0x35298 nn/nn2_ +W 0x3535c ustop/t_unasp2 +W 0x35468 ustop/k_unasp_ +W 0x35724 ustop/tsh_pzd_unasp +W 0x35f74 ustop/tsh_pzd +W 0x36ab4 ufric/sh_pzd2 +W 0x37604 ustop/ts_unasp +W 0x37e7c ustop/ts_rfx_unasp +W 0x38a68 ustop/ts_rfx +S 0x39748 vwl_zh/a_n +S 0x3984c vowel/aa_2 +S 0x399d0 vowel/a_3 +S 0x39ad4 vdiph/ai_6 +S 0x39c98 vwl_zh/aau +S 0x39ddc vowel/@_bck +S 0x39ee0 vowel/3_2 +S 0x3a024 vowelr/V3_r +S 0x3a168 vowel/ee_2 +S 0x3a26c vdiph2/ei_4 +S 0x3a3b0 vowel/ii_2 +S 0x3a4b4 vowel/i#_7 +S 0x3a5b8 vowel/i#_6 +S 0x3a6bc vwl_zh/iaa +S 0x3a800 vwl_zh/iaau +S 0x3a984 vwl_zh/ie +S 0x3aac8 vdiph2/iioo +S 0x3ac0c vwl_zh/iou +S 0x3ad90 vnasal/oo_n2 +S 0x3aed4 vdiph2/o_oo +S 0x3b018 vowel/8 +S 0x3b11c vdiph/8u +S 0x3b260 vwl_zh/ong +S 0x3b3e4 vnasal/u_n +S 0x3b4e8 vwl_zh/uaa +S 0x3b62c vdiph2/oa +S 0x3b770 vwl_zh/uai +S 0x3b8f4 vdiph2/o@ +S 0x3ba78 vwl_zh/uei +S 0x3bbfc vwl_zh/uo +S 0x3bd80 vowel/uu_3 +S 0x3be44 vowel/y_2 +S 0x3bf88 vdiph2/yu +S 0x3c10c vwl_zh/y& +S 0x3c250 vwl_zh/yee +S 0x3c394 vdiph2/y@ +S 0x3c4d8 vdiph/yi +S 0x3c61c vowel/ii_3 +S 0x3c720 vowel/oo_2 +S 0x3c824 vowel/i# +S 0x3c928 vowel/o_2 +S 0x3ca6c vdiph/aai_2 +S 0x3cc30 vdiph/ai_2 +S 0x3cdf4 vdiph2/iu_4 +S 0x3cf38 vdiph/ooi +S 0x3d0fc vdiph/ui_3 +S 0x3d280 vowel/a#_3 +S 0x3d384 r/a_ +S 0x3d448 vowel/i_4 +S 0x3d54c vowel/& +S 0x3d650 vowel/a_8 +S 0x3d754 vowel/o_5 +S 0x3d858 vowel/V_4 +S 0x3d95c vowel/yy +S 0x3da60 vowel/V +S 0x3db64 r/aa +S 0x3dc68 r2/r2@ +S 0x3dd2c r2/r2a +S 0x3ddf0 r2/r2e +S 0x3def4 r2/r2i +S 0x3dfb8 r2/r2o +S 0x3e07c r2/r2u +S 0x3e140 vowel/@_6 +S 0x3e244 vwl_en/@L +S 0x3e308 vowel/ee_5 +S 0x3e40c vowel/ii#_3 +S 0x3e510 vowel/ii_4 +S 0x3e614 vowel/ii_7 +S 0x3e718 vowel/0 +S 0x3e81c vowel/V_2 +S 0x3e920 vowel/8_2 +S 0x3ea24 vowel/uu +S 0x3eb28 vowel/3_en +S 0x3ec6c w/wi2 +S 0x3ed70 vowel/i_en +S 0x3eeb4 vowel/oo_en +S 0x3eff8 vwl_en/u_L +S 0x3f13c vdiph2/uw_2 +S 0x3f280 vdiph/au +S 0x3f404 vdiph/@u_en +S 0x3f588 vdiph2/ii@ +S 0x3f70c vdiph2/8@ +S 0x3f850 vdiph2/uu@ +S 0x3f9d4 vwl_en/aI@ +S 0x3fb98 vwl_en/aU@ +S 0x3fd1c vowelr/V_r +S 0x3fe60 vnasal/aa_n2 +S 0x3ffa4 vowel/oo_1 +S 0x400e8 vdiph/eei_2 +S 0x4022c vdiph/ooi_2 +S 0x403f0 vowel/aa_9 +S 0x404f4 vowel/aa +S 0x405f8 vowel/e# +S 0x406fc vowel/e_7 +S 0x40800 vowel/ee#_2 +S 0x40904 vowel/i_8 +S 0x40a08 vowel/i_7 +S 0x40b0c vowel/u_bck2 +S 0x40c10 vowel/u_bck3 +S 0x40d14 vowel/u_5 +S 0x40e18 vowel/8_7 +S 0x40f1c vowel/8_4 +S 0x41020 vdiph/&i +S 0x41164 vdiph/@i +W 0x412a8 ufric/s_pal +S 0x41b5c d/xd_pzd +W 0x41c20 x/d_pzd +S 0x42034 vdiph/ou_2 +S 0x42178 vowel/aa# +S 0x4227c vowel/uu_4 +S 0x42380 vdiph/aai_3 +S 0x42504 vdiph/y#i +S 0x42648 vdiph/ui_4 +S 0x4278c vdiph/aau +S 0x42910 vdiph/ou +S 0x42a54 vdiph/eu_2 +S 0x42b98 vdiph2/iu_2 +S 0x42d1c vdiph/&y +S 0x42e60 vdiph/eey +S 0x42fa4 vdiph/y#y +S 0x430e8 vdiph2/iy +S 0x4322c vdiph2/uo +S 0x43370 vdiph2/ie +S 0x434b4 vdiph2/y-y# +S 0x435f8 r3/r_trill_short +W 0x436fc h/hu_fi +S 0x43fec vowel/@_4 +S 0x440b0 vowel/ee_4 +S 0x441b4 vowel/u#_2 +S 0x442b8 vowel/oe_2 +S 0x443bc vwl_fr/y2r +S 0x44440 vwl_fr/e_2r +S 0x444c4 vwl_fr/aa2r +S 0x44548 vwl_fr/ee2r +S 0x4460c vwl_fr/oo2r +S 0x446d0 vwl_fr/@2r +S 0x44754 vwl_fr/a2r +S 0x447d8 vwl_fr/e2r +S 0x4485c vwl_fr/i2r +S 0x448e0 vwl_fr/o2r +S 0x44964 vwl_fr/u2r +S 0x449e8 vwl_fr/re2 +S 0x44a6c vwl_fr/r@2 +S 0x44af0 vwl_fr/raa +S 0x44b74 vwl_fr/ree +S 0x44bf8 vwl_fr/ry +S 0x44c7c vwl_fr/rw +S 0x44d00 vwl_fr/roo +S 0x44d84 vwl_fr/rj +S 0x44e08 vwl_fr/r@ +S 0x44e8c vwl_fr/ra +S 0x44f10 vwl_fr/re +S 0x44f94 vwl_fr/ri +S 0x45018 vwl_fr/ro +S 0x4509c vwl_fr/ru +S 0x45120 vwl_fr/r +S 0x451a4 vwl_fr/trr +S 0x45268 vwl_fr/rr +S 0x452ec vwl_fr/r_@ +S 0x45370 vwl_fr/r_a +S 0x453f4 vwl_fr/r_e +S 0x45478 vwl_fr/r_i +S 0x454fc vwl_fr/r_o +S 0x45580 vwl_fr/r_u +S 0x45604 vwl_fr/r_y +S 0x45688 vwl_fr/r_n +S 0x4574c vwl_fr/r_ +S 0x457d0 vwl_fr/tr +S 0x458d4 vwl_fr/br +S 0x459d8 vwl_fr/lo +S 0x45a5c l/l_y +S 0x45ae0 vowel/@_hgh +S 0x45ba4 vowel/a_7 +S 0x45ca8 vowel/e_8 +S 0x45dac vowel/e_mid +S 0x45eb0 vwl_fr/j +S 0x45fb4 vowel/o_8 +S 0x460f8 vowel/o_mid2 +S 0x461fc vwl_fr/wa +S 0x462c0 vnasal/aa_n4 +S 0x46404 vnasal/W_n +S 0x46548 vnasal/o_n5 +S 0x4668c b/xbr +S 0x46750 b/br +S 0x467d4 d/xdr +S 0x46898 g/xgr +S 0x4695c g/gr +W 0x46a20 x/g +W 0x46ce0 ustop/t_short_ +S 0x46f6c vwl_af/r@ +S 0x47030 vwl_af/@ +S 0x470f4 vowel/a_4 +S 0x471f8 vdiph/@u_3 +S 0x4737c vdiph2/i@ +S 0x47540 vdiph2/u@ +S 0x476c4 vnasal/a#_n2 +S 0x477c8 vnasal/a_n +S 0x4790c vnasal/e_n +S 0x47a10 vnasal/i_n +S 0x47b14 vnasal/o_n +S 0x47c58 vowel/yy_4 +S 0x47d5c vowel/o- +S 0x47e60 vwl_lv/a +S 0x47f64 vwl_lv/aa +S 0x48028 vwl_lv/e +S 0x480ec vwl_lv/ee +S 0x481b0 vwl_lv/i +S 0x48274 vwl_lv/ii +S 0x48338 vwl_lv/o +S 0x483fc vwl_lv/oo +S 0x484c0 vwl_lv/u +S 0x48584 vwl_lv/uu +S 0x48648 vdiph/aai +S 0x487cc vdiph2/ie_2 +S 0x48910 vdiph2/ua +S 0x48a14 vowel/@_low +S 0x48ad8 vowel/V_3 +S 0x48bdc vowel/i_fnt +S 0x48ce0 vowel/ii_6 +S 0x48da4 vowel/e_2 +S 0x48ea8 vdiph/ee-e +S 0x48fec vowel/a_5 +S 0x490f0 vowel/uu_bck +S 0x491f4 vnasal/i_n2 +S 0x492f8 vnasal/ii_n +S 0x493fc vnasal/ee_n2 +S 0x49540 vnasal/V_n +S 0x49644 vdiph/aau_3 +S 0x497c8 d/xd3 +W 0x4988c ustop/th_rfx2 +S 0x49e90 g2/xg +W 0x49f54 ustop/percus02 +W 0x4a284 ustop/ts2 +S 0x4a754 vowel/y#_2 +S 0x4a858 vowel/e_3 +S 0x4a95c vowel/e_e +S 0x4aa60 vowel/a#_2 +S 0x4ab64 vowel/oo_5 +S 0x4ac68 vowel/y## +S 0x4ad6c vowel/y#_3 +S 0x4ae70 vdiph/ai_7 +S 0x4aff4 vdiph/ou_3 +S 0x4b0f8 vdiph/y#i_2 +S 0x4b23c m/m#_ +S 0x4b340 n/n#_ +S 0x4b444 n^/n^#_ +S 0x4b548 nn/nn#_ +W 0x4b64c ufric/tl# +S 0x4bfb4 r3/r#_ +S 0x4c038 vowel/oo_3 +W 0x4c0fc ustop/k_asp_u +W 0x4c92c ufric/x2 +S 0x4d284 vowel/aa_6 +S 0x4d3c8 vowel/u#_7 +S 0x4d4cc vowel/V_6 +W 0x4d5d0 ustop/t_unasp +W 0x4d708 ustop/ts_pzd3 +S 0x4dd88 vowel/i_2 +S 0x4de8c vdiph/aau_2 +S 0x4dfd0 vdiph/ae +S 0x4e114 vdiph/eeu_2 +S 0x4e258 vdiph/ae_2 +S 0x4e3dc vdiph/eei_5 +S 0x4e520 vwl_ru/ee +S 0x4e664 vdiph2/ea +S 0x4e7a8 vowel/i_3 +S 0x4e8ac vowel/i_6 +S 0x4e9b0 vdiph2/uaa +S 0x4eb34 vdiph/eei_3 +S 0x4ec78 vwl_lv/e2 +S 0x4ed3c vwl_lv/ee2 +S 0x4ee00 vwl_lv/y +S 0x4ef04 vwl_lv/yy +S 0x4f008 vwl_lv/ai +S 0x4f18c vwl_lv/au +S 0x4f310 vwl_lv/ei +S 0x4f454 vwl_lv/ie +S 0x4f598 vwl_lv/iu +S 0x4f71c vwl_lv/ui +S 0x4f860 vwl_lv/ua +S 0x4f964 vwl_lv/oi +W 0x4fb28 h/h-lv +S 0x504a8 l^/l^_ +W 0x505ac myanmar/k +W 0x5149c myanmar/kh +W 0x520b4 myanmar/g.wav +S 0x52ef8 myanmar/ny +W 0x530bc myanmar/s +W 0x54258 myanmar/hs +W 0x553f4 myanmar/z +W 0x564dc myanmar/t_short +W 0x5662c myanmar/ht +W 0x571b4 myanmar/d +W 0x57d28 myanmar/p +W 0x58c54 myanmar/t_hi +W 0x58f20 myanmar/h +S 0x5a228 myanmar/yy +S 0x5a42c myanmar/a +S 0x5a4b0 myanmar/ky +W 0x5a6f4 myanmar/by.wav +W 0x5ac00 myanmar/ch.wav +W 0x5b454 myanmar/gya.wav +W 0x5c7d8 myanmar/htya.wav +W 0x5db1c myanmar/phya.wav +W 0x5e7d4 myanmar/pya.wav +W 0x5fc58 myanmar/ty.wav +W 0x60f18 myanmar/sh.wav +S 0x61b2c myanmar/a01 +S 0x61c30 myanmar/a02 +S 0x61db4 myanmar/a03 +S 0x62038 myanmar/a04 +S 0x620fc myanmar/a05 +S 0x62200 myanmar/a06 +S 0x62584 myanmar/a07 +S 0x626c8 myanmar/a08 +S 0x6284c myanmar/a09 +S 0x62b50 myanmar/a11 +S 0x62cd4 myanmar/a12 +S 0x62f98 myanmar/a14 +S 0x6315c myanmar/a13 +S 0x633e0 myanmar/a17 +S 0x636a4 myanmar/a16 +S 0x639a8 myanmar/a20 +S 0x63b2c myanmar/a19 +S 0x63d70 myanmar/a21 +S 0x64034 myanmar/a23 +S 0x64138 myanmar/a22 +S 0x642bc myanmar/a24 +S 0x64600 myanmar/a29 +S 0x64744 myanmar/a28 +S 0x64988 myanmar/a30 +S 0x64d0c myanmar/a32 +S 0x64f50 myanmar/a31 +S 0x65214 myanmar/a33 +S 0x65318 myanmar/a35 +S 0x655dc myanmar/a34 +S 0x658a0 myanmar/a36 +S 0x65ba4 myanmar/a43 +S 0x65de8 myanmar/a42 +S 0x6602c myanmar/a44 +S 0x66430 myanmar/a49 +S 0x665b4 myanmar/a50 +S 0x66838 myanmar/a46 +S 0x66a3c myanmar/a45 +S 0x66c40 myanmar/a47 +S 0x66f44 myanmar/a25 +S 0x67088 myanmar/a26 +S 0x672cc myanmar/a27 +S 0x67490 myanmar/a37 +S 0x67654 myanmar/a38 +S 0x67798 myanmar/a39 +S 0x679dc myanmar/a40 +S 0x67b60 myanmar/a41 +S 0x67ce4 vowel/y_4 +S 0x67de8 vwl_no/y# +S 0x67eec vwl_no/& +S 0x67ff0 vwl_no/u# +S 0x680f4 vwl_no/u#2 +S 0x68238 vdiph/0i_2 +S 0x683bc vdiph/ai_3 +S 0x68500 vwl_no/y#y +S 0x68644 vwl_no/au- +S 0x68808 vowel/ee# +S 0x6890c vnasal/ee_u_n +S 0x68a90 vnasal/oo_n3 +S 0x68c14 vowel/aa_3 +W 0x68d18 vocw/Q2 +W 0x695e4 ustop/tsh_asp +W 0x6a24c x/g3 +S 0x6a434 vwl_ro/mi +S 0x6a578 vwl_ru/ii- +S 0x6a63c vwl_ru/ii +S 0x6a740 vwl_ru/ii# +S 0x6a804 vwl_ru/i# +S 0x6a908 vwl_ru/e +S 0x6aa0c vwl_ru/E# +S 0x6ab10 vwl_ru/E@ +S 0x6ac14 vwl_ru/o +S 0x6ad18 vwl_ru/oo +S 0x6addc vwl_ru/u +S 0x6aee0 vwl_ru/u# +S 0x6b024 vwl_ru/u#u +S 0x6b168 vwl_ru/8 +S 0x6b22c vwl_ru/ju +S 0x6b330 vwl_ru/ja +S 0x6b4b4 vwl_ru/a +S 0x6b5b8 vwl_ru/aa +S 0x6b6bc r3/r_ru2 +W 0x6b7c0 r3/r_ru +S 0x6bac4 vwl_it/o_open +S 0x6bbc8 vdiph/eeu +S 0x6bd0c vdiph/au_2 +S 0x6be90 vdiph/ooi_3 +S 0x6c014 vdiph/aau_4 +S 0x6c198 vowel/8_6 +S 0x6c29c vdiph/ooi_4 +S 0x6c3e0 vdiph2/ye +S 0x6c524 l/l_front_ +S 0x6c668 l/l_front +S 0x6c76c l/l_4 +S 0x6c830 vowel/ee_6 +S 0x6c8f4 vowel/y_5 +S 0x6ca38 vowel/yy_3 +S 0x6cb3c vowel/oe_4 +S 0x6cc00 vowel/aa_4 +W 0x6cd04 ufric/sx_sv +S 0x6d54c vowel/o_4 +W 0x6d650 ustop/t_hard +W 0x6d890 ufric/sh3 +S 0x6e224 vwl_tn/r@ +S 0x6e2e8 vwl_tn/@ +S 0x6e3ac vwl_tn/I +S 0x6e470 vdiph/i@_2 +S 0x6e5f4 vowel/0_2 +W 0x6e6f8 ufric/tlh +E 0x6fc14 envelope/p_fallrise +E 0x6fc94 envelope/vi_5amp +E 0x6fd14 envelope/p_512 +E 0x6fd94 envelope/vi_6amp +S 0x6fe14 vietnam/a_2 +S 0x6ff58 vietnam/aa +S 0x7005c vietnam/e_e_2 +S 0x70160 vietnam/e +S 0x70264 vietnam/e_short_1 +S 0x703e8 vietnam/i +S 0x704ec vietnam/i_2 +S 0x705f0 vietnam/oo +S 0x70734 vietnam/o_2 +S 0x70878 vietnam/u +S 0x7097c vietnam/y_2 +S 0x70a80 vietnam/V_2 +S 0x70b84 vietnam/@_2 +S 0x70c88 vdiph/&i_2 +S 0x70dcc vdiph/u-i +S 0x70f10 vdiph/@u +S 0x71054 vdiph2/ii@_3 +S 0x711d8 vietnam/y@ +S 0x7131c vietnam/u@ +S 0x714a0 vietnam/o# +S 0x715e4 vietnam/O_short_2 +S 0x71728 vietnam/oe +S 0x7186c vietnam/ie +S 0x719b0 vnasal/oi_n +S 0x71b74 vdiph/@i_3 +S 0x71cb8 vowel/u_3 +W 0x71dbc vietnam/c_2 +W 0x722f8 vietnam/c +S 0x72834 n/nm +S 0x728f8 l/l_vi +W 0x729fc vietnam/th +W 0x72fec vietnam/tr +W 0x732dc vietnam/dda_2 +W 0x737a8 vietnam/ch +W 0x73d04 vietnam/w +S 0x73f5c vietnam/_w +S 0x74020 vietnam/w_ +S 0x740e4 vietnam/n^_ +W 0x74228 vietnam/hi +W 0x74594 vietnam/hu +S 0x74c18 vwl_zh/ang +S 0x74e1c vwl_zh/aang +S 0x75060 vwl_zh/eng +S 0x75264 vwl_zh/ing +S 0x754a8 vwl_zh/ng +S 0x755ec vwl_zh/oeng +S 0x757b0 vwl_zh/ung +S 0x75934 vowel/8_3 +S 0x75a38 vdiph/y#y_2 +W 0x75bbc ustop/tsh_sr +E 0x764d4 envelope/chr_fall21 +E 0x76554 envelope/chr_level2 +E 0x765d4 envelope/chr_rise23 +E 0x76654 envelope/chr_fall32 +E 0x766d4 envelope/chr_level3 +E 0x76754 envelope/chr_rise4 +E 0x767d4 envelope/chr_fall43 +S 0x76854 vnasal/a#_n +W 0x76958 x/dz_pzd_ +S 0x77048 vwl_es/oo_ +S 0x7710c vwl_es/ooi_ +S 0x77290 vwl_es/ooi +S 0x77414 voc/v2 +W 0x774d8 ufric/z_eu +W 0x77ee0 ufric/ts_eu +W 0x78888 ufric/tz_eu +S 0x792f4 vdiph/0i +S 0x794b8 vdiph/oou +S 0x795fc vwl_it/a +S 0x79700 vwl_it/e_open +S 0x79804 vwl_it/i +S 0x79908 vwl_it/o +S 0x79a4c vwl_it/u +S 0x79b50 vowel/8_5 +S 0x79c54 vowel/o_7 +S 0x79d58 vdiph/eeu_3 +S 0x79e9c vnasal/a#u_n +S 0x79fe0 vowel/ee_3 +S 0x7a0a4 d/x_tap +S 0x7a168 d/tap2 +S 0x7a22c vwl_ro/li +S 0x7a330 vwl_ro/ni +S 0x7a434 vwl_ro/ii- +S 0x7a4f8 vowel/i#_5 +S 0x7a5fc vdiph/ii +S 0x7a7c0 vdiph/i#i +S 0x7a904 vdiph2/uw_3 +S 0x7aa48 vdiph2/eo +S 0x7abcc vdiph2/e_u +S 0x7ad10 d/tap_i +S 0x7add4 d/tap +S 0x7ae98 vowel/u# +S 0x7af9c vowel/@_fr +S 0x7b060 vowel/o-_2 +S 0x7b164 vowel/aa_5 +S 0x7b2a8 vwl_en_n/O@ +S 0x7b3ac vdiph2/uw_4 +S 0x7b4f0 vdiph2/ee@ +S 0x7b634 vowel/ii_final +S 0x7b738 vowel/o-_3 +S 0x7b83c vwl_en_rp/aa +S 0x7b980 vowel/3_3 +S 0x7bac4 vowel/uu#_2 +S 0x7bbc8 vdiph/au_3 +S 0x7bd4c vdiph/@u_2 +S 0x7be90 vdiph2/ei_2 +S 0x7bfd4 vdiph2/ee@_2 +S 0x7c118 vwl_en_rp/i@ +S 0x7c29c vwl_en_rp/aU@ +S 0x7c420 vowel/e_5 +S 0x7c524 vowel/u#_4 +S 0x7c628 vowelr/aa_r +S 0x7c7ec vowelr/e_r +S 0x7c970 vowel/i_5 +S 0x7ca74 vwl_en_us/or +S 0x7cbb8 vowelr/oo_r +S 0x7ccbc vdiph/au# +S 0x7ce00 vowel/o_3 +S 0x7cf44 vwl_en/aI@_2 +S 0x7d0c8 vdiph2/e@ +S 0x7d20c vowelr/i_r +S 0x7d350 vdiph2/u#@ +S 0x7d494 vwl_en/@L_2 +S 0x7d598 vwl_en_us/3_us +S 0x7d69c vowel/@_low2 +S 0x7d760 vwl_en_us/ar +S 0x7d8a4 vwl_en_us/a +S 0x7d9a8 vwl_en_us/ee +S 0x7daac vwl_en_us/aar +S 0x7dc30 vwl_en_us/3_us2 +S 0x7dd74 vwl_en_us/oor +S 0x7def8 vdiph2/uw_6 +S 0x7e03c vdiph/aoo +S 0x7e180 vwl_en_us/ai +S 0x7e304 vwl_en_us/er +S 0x7e488 vwl_en_us/ir +S 0x7e60c vwl_en_us/ur +S 0x7e750 vwl_en_us/ai@ +S 0x7e8d4 vwl_en_us/ai3 +S 0x7ead8 vwl_en_us/aU@ +S 0x7ec9c klatt/x_tap +S 0x7ed60 klatt/tap2 +S 0x7ee24 vwl_en_us_nyc/a_raised +S 0x7ef28 vwl_en_us_nyc/a +S 0x7f02c vwl_en_us_nyc/0_3 +S 0x7f130 vwl_en_us_nyc/aa_8 +S 0x7f234 vwl_en_us_nyc/@i +S 0x7f3f8 vowel/@_fnt +S 0x7f4fc vdiph2/ei_3 +S 0x7f640 vdiph/Vu_2 +S 0x7f7c4 vdiph2/i@_2 +S 0x7f948 vwl_en/ooi@ +S 0x7fb0c vwl_af/I +S 0x7fbd0 l/L_eL_af +S 0x7fc94 vowel/y_3 +S 0x7fd98 vdiph2/iu_3 +S 0x7ff5c vdiph/Vu +S 0x800e0 vdiph/ai_4 +S 0x80264 vdiph/oi_2 +S 0x80428 vdiph/ui_2 +S 0x805ac vdiph2/y#@ +S 0x806f0 vnasal/aa_n3 +S 0x80834 vnasal/o_n2 +S 0x80978 vdiph/aau_6 +S 0x80afc vwl_de/y# +S 0x80c00 l/l_3 +S 0x80c84 j/_j_short +S 0x80d08 vdiph2/i@_3 +S 0x80e8c vwl_de/uu_@ +S 0x80fd0 vdiph2/ii@_2 +S 0x81154 vowel/ii_8 +S 0x81258 vowel/y#_4 +S 0x8135c vowel/o_6 +S 0x81460 vowel/a#_4 +S 0x81564 vdiph/y#y_3 +S 0x81668 vdiph/ou_4 +S 0x817ac voc/Q_less +S 0x81870 vnasal/&_n +W 0x81974 ustop/ki_ejc +S 0x81d80 vdiph/Vi +S 0x81f04 vowel/u_6 +S 0x82008 vowel/u#_3 +S 0x8210c vdiph/ai_8 +S 0x82290 voc/murmur1 +S 0x823d4 vdiph/@i_4 +S 0x82558 vnasal/ai_n +S 0x8269c vdiph/a#u +S 0x82820 vnasal/au_n +S 0x82964 d/dr2 +S 0x82a28 vowel/&_2 +W 0x82b2c ustop/tsh_unasp2 +W 0x82f74 r3/rz_cs +S 0x83a20 voc/zh_2 +W 0x83ae4 ustop/tsh2 +S 0x841f0 dzh/dzh2 +W 0x842b4 ustop/t_sr +S 0x845ec d/d_dnt +W 0x846b0 ufric/ch_sr +W 0x85290 ufric/x_sr +W 0x85b10 ustop/ts_sr +S 0x862cc vowel/&_3 +S 0x863d0 vwl_fr/@R +S 0x864d4 vietnam/a diff --git a/system/tts_engines/piper/engine/espeak-ng-data/phonindex b/system/tts_engines/piper/engine/espeak-ng-data/phonindex new file mode 100644 index 0000000..03ba806 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/phonindex differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/phontab b/system/tts_engines/piper/engine/espeak-ng-data/phontab new file mode 100644 index 0000000..5c25ca1 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/phontab differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/piqd_dict b/system/tts_engines/piper/engine/espeak-ng-data/piqd_dict new file mode 100644 index 0000000..7924788 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/piqd_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/pl_dict b/system/tts_engines/piper/engine/espeak-ng-data/pl_dict new file mode 100644 index 0000000..dcb4132 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/pl_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/pt_dict b/system/tts_engines/piper/engine/espeak-ng-data/pt_dict new file mode 100644 index 0000000..c801973 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/pt_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/py_dict b/system/tts_engines/piper/engine/espeak-ng-data/py_dict new file mode 100644 index 0000000..47b8123 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/py_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/qdb_dict b/system/tts_engines/piper/engine/espeak-ng-data/qdb_dict new file mode 100644 index 0000000..18a6895 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/qdb_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/qu_dict b/system/tts_engines/piper/engine/espeak-ng-data/qu_dict new file mode 100644 index 0000000..1894201 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/qu_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/quc_dict b/system/tts_engines/piper/engine/espeak-ng-data/quc_dict new file mode 100644 index 0000000..2361f7b Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/quc_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/qya_dict b/system/tts_engines/piper/engine/espeak-ng-data/qya_dict new file mode 100644 index 0000000..9468943 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/qya_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/ro_dict b/system/tts_engines/piper/engine/espeak-ng-data/ro_dict new file mode 100644 index 0000000..22f7dfb Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/ro_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/ru_dict b/system/tts_engines/piper/engine/espeak-ng-data/ru_dict new file mode 100644 index 0000000..ac39ad9 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/ru_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/sd_dict b/system/tts_engines/piper/engine/espeak-ng-data/sd_dict new file mode 100644 index 0000000..51831c6 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/sd_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/shn_dict b/system/tts_engines/piper/engine/espeak-ng-data/shn_dict new file mode 100644 index 0000000..eaf1a01 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/shn_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/si_dict b/system/tts_engines/piper/engine/espeak-ng-data/si_dict new file mode 100644 index 0000000..9634893 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/si_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/sjn_dict b/system/tts_engines/piper/engine/espeak-ng-data/sjn_dict new file mode 100644 index 0000000..ba76f7c Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/sjn_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/sk_dict b/system/tts_engines/piper/engine/espeak-ng-data/sk_dict new file mode 100644 index 0000000..cd99f8f Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/sk_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/sl_dict b/system/tts_engines/piper/engine/espeak-ng-data/sl_dict new file mode 100644 index 0000000..e13eb49 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/sl_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/smj_dict b/system/tts_engines/piper/engine/espeak-ng-data/smj_dict new file mode 100644 index 0000000..203230f Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/smj_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/sq_dict b/system/tts_engines/piper/engine/espeak-ng-data/sq_dict new file mode 100644 index 0000000..fccdbf3 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/sq_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/sr_dict b/system/tts_engines/piper/engine/espeak-ng-data/sr_dict new file mode 100644 index 0000000..63576dd Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/sr_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/sv_dict b/system/tts_engines/piper/engine/espeak-ng-data/sv_dict new file mode 100644 index 0000000..a4e3f6d Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/sv_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/sw_dict b/system/tts_engines/piper/engine/espeak-ng-data/sw_dict new file mode 100644 index 0000000..601f310 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/sw_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/ta_dict b/system/tts_engines/piper/engine/espeak-ng-data/ta_dict new file mode 100644 index 0000000..7e51266 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/ta_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/te_dict b/system/tts_engines/piper/engine/espeak-ng-data/te_dict new file mode 100644 index 0000000..36167c2 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/te_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/th_dict b/system/tts_engines/piper/engine/espeak-ng-data/th_dict new file mode 100644 index 0000000..30d9e62 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/th_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/tk_dict b/system/tts_engines/piper/engine/espeak-ng-data/tk_dict new file mode 100644 index 0000000..e0dace4 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/tk_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/tn_dict b/system/tts_engines/piper/engine/espeak-ng-data/tn_dict new file mode 100644 index 0000000..ffa3962 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/tn_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/tr_dict b/system/tts_engines/piper/engine/espeak-ng-data/tr_dict new file mode 100644 index 0000000..69d521f Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/tr_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/tt_dict b/system/tts_engines/piper/engine/espeak-ng-data/tt_dict new file mode 100644 index 0000000..ef0b02b Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/tt_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/ug_dict b/system/tts_engines/piper/engine/espeak-ng-data/ug_dict new file mode 100644 index 0000000..25f827f Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/ug_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/uk_dict b/system/tts_engines/piper/engine/espeak-ng-data/uk_dict new file mode 100644 index 0000000..326dc44 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/uk_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/ur_dict b/system/tts_engines/piper/engine/espeak-ng-data/ur_dict new file mode 100644 index 0000000..9c170ad Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/ur_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/uz_dict b/system/tts_engines/piper/engine/espeak-ng-data/uz_dict new file mode 100644 index 0000000..c58fae5 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/uz_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/vi_dict b/system/tts_engines/piper/engine/espeak-ng-data/vi_dict new file mode 100644 index 0000000..bbd709e Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/vi_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Alex b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Alex new file mode 100644 index 0000000..10c6b6e --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Alex @@ -0,0 +1,10 @@ +language variant +name Alex + +voicing 70 +pitch 105 115 +flutter 0 + +formant 1 110 115 100 +formant 2 100 110 100 +formant 3 100 80 75 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Alicia b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Alicia new file mode 100644 index 0000000..ff64799 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Alicia @@ -0,0 +1,22 @@ +language variant +name Alicia +gender female +pitch 180 275 +echo 40 50 +formant 0 115 115 110 +formant 1 130 160 120 +formant 2 150 110 150 +formant 3 135 150 100 +formant 4 120 120 120 +formant 5 120 120 120 +formant 6 100 110 105 +formant 7 100 110 160 +formant 8 200 120 100 +intonation 2 +voicing 38 +consonants 100 20 +roughness 1 +stressAdd 1 64 64 50 50 100 100 200 +stressAmp 12 12 20 20 12 12 20 20 +breathw 150 150 200 200 400 400 600 600 +breath 0 4 5 2 3 13 3 2 \ No newline at end of file diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Andrea b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Andrea new file mode 100644 index 0000000..39fb186 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Andrea @@ -0,0 +1,20 @@ +language variant +name Andrea +gender female + +pitch 200 265 +roughness 0 + +formant 0 100 100 100 +formant 1 110 100 80 +formant 2 110 80 80 +formant 3 115 110 80 +formant 4 115 80 100 +formant 5 95 50 100 +formant 6 0 0 0 +formant 7 120 100 100 +formant 8 110 100 100 +intonation 3 +stressLength 0 1 2 3 4 5 6 7 +stressAdd 130 140 140 100 0 0 130 160 +voicing 150 \ No newline at end of file diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Andy b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Andy new file mode 100644 index 0000000..6781574 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Andy @@ -0,0 +1,19 @@ +language variant +name Andy +gender Male + +pitch 85 110 + +flutter 0 +formant 0 80 80 80 80 +formant 1 100 100 100 120 +formant 2 100 88 100 +formant 3 0 0 0 +formant 4 80 80 80 +formant 5 80 80 80 +formant 6 0 0 0 +formant 7 0 0 0 +formant 8 0 0 0 +stressAdd 0 0 0 0 0 0 0 200 +stressAmp 35 35 35 35 35 35 35 35 35 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Annie b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Annie new file mode 100644 index 0000000..42a38ef --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Annie @@ -0,0 +1,17 @@ +language variant +name Annie +gender female +pitch 120 280 + +formant 0 105 125 120 +formant 1 120 140 120 +formant 2 120 150 140 +formant 3 130 150 130 +formant 4 120 120 110 +formant 5 120 120 110 +formant 6 120 140 130 +formant 7 120 140 130 +formant 8 120 140 130 +intonation 1 +voicing 30 +consonants 110 120 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/AnxiousAndy b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/AnxiousAndy new file mode 100644 index 0000000..3a190b1 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/AnxiousAndy @@ -0,0 +1,19 @@ +language variant +name anxiousAndy +gender Male + +pitch 115 110 + +flutter 0 +formant 0 80 80 80 80 +formant 1 100 100 100 120 +formant 2 100 100 100 +formant 3 0 0 0 +formant 4 0 0 0 +formant 5 100 100 100 +formant 6 100 100 100 +formant 7 100 100 100 +formant 8 100 100 100 +stressAdd 100 100 100 100 100 100 100 300 +stressAmp 35 35 35 35 35 35 35 35 35 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Demonic b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Demonic new file mode 100644 index 0000000..0bbb225 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Demonic @@ -0,0 +1,65 @@ +##Ten en cuenta que los 2 signos de número en este archivo tienen explicaciones de las comfiguraciones que puede aplicar y cómo comfigurarlas +## Language Establece el idioma de la voz. Esta opción es necesaria para cualquier comfiguración que realices +##La siguiente línea es una configuración que puede cambiar. Sin embargo, si no conoce los códigos de idioma, puede ser mejor dejar la configuración tal y como está. +language variant + +## La configuración de nombre es el nombre que aparecerá en la configuración de voz en el cuadro combinado de variante. +##La siguiente línea es una opción que puede cambiar +name Demonic +##La siguiente línea establece el género de la voz. Male or Female (hombre o mujer) +##La siguiente línea es una opción que puede cambiar +gender male + +flutter 5 +stressAmp 20 18 20 20 20 22 22 22 +##Las opciones de formantes +## Formant 0 es usado para dar una baja frecuencia a los sonnidos +## Los tres números son frecuencia, fuerza y ancho, en orden. Ten en cuenta que los números están separados por espacios +##La siguiente línea es una opción que puede cambiar +formant 0 100 100 100 + +# Formant 1, 2, y 3 son las 3 formantes estándar para definir las vocales. +##Las siguientes 3 líneas son opciones que puedes cambiar +formant 1 70 100 100 +formant 2 80 100 90 +formant 3 80 160 90 + +# Formants 4 y 5 afectan a f3. Esto afectará la calidad de la voz. +##Las siguientes 2 líneas son comfiguraciones que puede cambiar. +formant 4 80 85 +formant 5 100 100 80 + +## Formantes 6, 7 y 8 son opciones que te ofrecen un sonido más claro de las vocales +##Las siguientes 3 líneas son opciones que puedes cambiar +formant 6 80 80 100 +formant 7 130 130 110 +formant 8 120 120 150 + +##Intonation afecta el ascenso y la caída de la voz +## Las opciones son: 1 predeterminado, 2 entonación media, 3 entonación media y no afecta a las comas, 4 al final de la oración o punto aumenta el tono de la voz. +##La siguiente línea es una opción que puedes cambiar. +intonation 10 + +# Establecer el rango de tono. El primer número le da un tono base a la voz (valor en hz). El segundo número controla el rango de tonos usado por la voz. Poniéndolo igual +# si los 2 números son iguales, la voz será monótona. Por defecto los ajustes son 82 y 118 +pitch 43 120 +## La configuración del tono. El primer número en la línea de configuración, 600, es la configuración de frecuencia para la cantidad de graves en la voz. +## El segundo número en la línea de tono es el volumen de la frecuencia de graves. Puede configurarlo de 0 a 255, siendo 0 la menor cantidad, 255 la mayor. +##El tercer número en la línea de tono, 1200, es la frecuencia de rango medio. El cuarto número en la línea es la configuración para cambiar el volumen de la frecuencia de rango medio. +##0 es la menor cantidad y 255 es la mayor. +## El quinto número en la línea de tono, 2000, es la frecuencia de agudos. El sexto número es el volumen de la frecuencia de agudos. 0 es el mínimo y 255 es el máximo. +## Notará que las 3 frecuencias están configuradas en 255. +###La siguiente línea es una opción que puedes cambiar. +tone 100 255 1200 255 1500 255 +echo 8 10000 +roughness 3 +breath 20 5 2 10 5 0 27 100 +breathw 255 255 60 180 160 255 255 255 +consonants 194 255 +voicing 65 +stressLength 0 1 2 3 4 5 6 7 +stressAdd 250 350 700 500 450 290 100 225 +stressAmp 16 16 24 24 16 16 20 24 +##Este archivo no incluye todas las configuraciones que se pueden usar para modificar una voz E Speak. Su objetivo es familiarizarlo con lo que hace la configuración. +##Sin envargo puedes visitar la página http://espeak.sourceforge.net/voices.html y consultar más información acerca de cómo agregar o cambiar otras configuraciones. +## Espero que te haya servido esta ayuda, y que te hayas divertido. diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Denis b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Denis new file mode 100644 index 0000000..ad1d72e --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Denis @@ -0,0 +1,19 @@ +language variant +name Denis +gender male 35 +pitch 80 115 +flutter 0 +roughness 0 + + +formant 0 100 160 160 +formant 1 95 95 95 +formant 2 100 100 100 +formant 3 90 90 90 +formant 4 40 40 40 +formant 5 80 80 80 +formant 6 10 10 10 +formant 7 10 10 10 +formant 8 10 10 10 +voicing 40 +consonants 80 80 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Diogo b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Diogo new file mode 100644 index 0000000..1e51396 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Diogo @@ -0,0 +1,22 @@ +language variant +name Diogo +gender male 25 +pitch 82 122 +echo 0 0 +flutter 0 +roughness 0 +stressAmp 20 18 20 20 20 22 22 22 + + +formant 0 105 200 140 +formant 1 95 150 120 +formant 2 100 120 140 +formant 3 95 95 140 +formant 4 30 30 30 -100 +formant 5 90 90 90 +formant 6 110 60 65 +formant 7 100 0 100 +formant 8 100 0 100 +voicing 35 +consonants 60 40 +tone 60 250 140 100 1000 50 3500 35 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Gene b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Gene new file mode 100644 index 0000000..5799e7e --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Gene @@ -0,0 +1,17 @@ +language variant +name Gene + +pitch 80 110 + +formant 0 120 120 120 +formant 1 90 100 110 +formant 2 100 100 95 +formant 3 90 100 100 +formant 4 90 100 110 +formant 5 90 110 110 +formant 6 100 70 100 +formant 7 100 70 100 +formant 8 100 80 100 +voicing 120 +consonants 50 110 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Gene2 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Gene2 new file mode 100644 index 0000000..5e4ccb6 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Gene2 @@ -0,0 +1,17 @@ +language variant +name Gene2 + +pitch 100 130 + +formant 0 120 120 120 +formant 1 90 100 110 +formant 2 100 100 95 +formant 3 90 100 100 +formant 4 90 100 110 +formant 5 90 110 110 +formant 6 100 70 100 +formant 7 100 70 100 +formant 8 100 80 100 +voicing 120 +consonants 50 110 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Henrique b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Henrique new file mode 100644 index 0000000..d3fa3d7 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Henrique @@ -0,0 +1,22 @@ +language variant +name Henrique +gender male 25 +pitch 70 130 +echo 0 0 +flutter 0 +roughness 0 +stressAmp 20 18 20 20 20 22 22 22 + + +formant 0 105 200 140 +formant 1 95 150 120 +formant 2 100 120 140 +formant 3 95 95 140 +formant 4 30 30 30 -100 +formant 5 90 90 90 +formant 6 110 60 65 +formant 7 100 0 100 +formant 8 100 0 100 +voicing 35 +consonants 60 40 +tone 70 250 230 80 1100 30 3500 40 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Hugo b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Hugo new file mode 100644 index 0000000..fe79235 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Hugo @@ -0,0 +1,22 @@ +language variant +name Hugo +gender male 25 +pitch 70 130 +echo 0 0 +flutter 0 +roughness 0 +stressAmp 20 18 20 20 20 22 22 22 + + +formant 0 105 200 140 +formant 1 95 150 120 +formant 2 100 120 140 +formant 3 95 95 140 +formant 4 30 30 30 -100 +formant 5 90 90 90 +formant 6 110 60 65 +formant 7 100 0 100 +formant 8 100 0 100 +voicing 35 +consonants 60 40 +tone 400 160 1100 90 3500 90 150 35 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Jacky b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Jacky new file mode 100644 index 0000000..485a455 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Jacky @@ -0,0 +1,17 @@ +language variant +name Jacky + +pitch 85 130 + +formant 0 150 155 100 +formant 1 90 155 70 +formant 2 95 70 64 +formant 3 15 20 30 +formant 4 20 30 40 +formant 5 65 20 65 +formant 6 70 80 100 +formant 7 20 80 100 +formant 8 100 95 80 +voicing 135 +consonants 110 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Lee b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Lee new file mode 100644 index 0000000..75acf8e --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Lee @@ -0,0 +1,20 @@ +language variant +name Lee +gender Male + +#echo 230 30 +pitch 85 110 + +flutter 0 +formant 0 80 80 80 80 +formant 1 80 80 100 100 +formant 2 80 80 80 +formant 3 9 9 9 +formant 4 290 290 +formant 5 130 0 0 +formant 6 90 90 90 +formant 7 90 90 90 +formant 8 90 90 90 +stressAdd 0 0 0 200 0 0 0 100 +stressAmp 30 30 30 30 30 30 30 30 30 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Marco b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Marco new file mode 100644 index 0000000..6d39301 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Marco @@ -0,0 +1,23 @@ +language variant +name Marco +gender male 30 +intonation 1 +pitch 100 152 +echo 50 80 +flutter 2 +roughness 0 +stressAmp 25 25 24 20 38 31 39 27 +stressAdd 250 125 250 250 225 145 50 256 + +formant 0 100 120 130 +formant 1 75 180 170 +formant 2 92 120 110 +formant 3 140 120 110 +formant 4 10 20 20 -50 +formant 5 110 70 20 +formant 6 140 100 98 +formant 7 130 120 115 +formant 8 105 120 108 +voicing 38 +consonants 90 140 +tone 420 150 1200 135 3000 70 4700 40 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Mario b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Mario new file mode 100644 index 0000000..3f950de --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Mario @@ -0,0 +1,17 @@ +language variant +name Mario + +pitch 75 125 + +formant 0 100 111 95 +formant 1 100 111 60 +formant 2 95 90 55 +formant 3 100 50 65 +formant 4 69 65 65 +formant 5 79 60 75 +formant 6 89 60 75 +formant 7 99 0 100 +formant 8 109 0 100 +voicing 135 +consonants 115 120 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Michael b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Michael new file mode 100644 index 0000000..c6ed713 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Michael @@ -0,0 +1,17 @@ +language variant +name Michael + +pitch 75 125 + +formant 0 105 111 95 +formant 1 85 111 60 +formant 2 95 90 55 +formant 3 59 50 65 +formant 4 69 65 65 +formant 5 79 60 75 +formant 6 89 60 75 +formant 7 99 0 100 +formant 8 109 0 100 +voicing 135 +consonants 115 120 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Mike b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Mike new file mode 100644 index 0000000..93ea4d8 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Mike @@ -0,0 +1,7 @@ +language variant +name Mike +voicing 70 +formant 1 96 97 100 +formant 2 96 97 100 +formant 5 95 103 100 +pitch 67 107 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Mr serious b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Mr serious new file mode 100644 index 0000000..78a76ed --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Mr serious @@ -0,0 +1,50 @@ +##Please note the 2 number signs, or pound signs in this file are for comments to help you to understand what the settings are and how to set them. +## Language sets the language of your voice. This setting is required for every voice that you make. +##The next line is a setting you can change. However if you don't know the language codes it may be best to leave the setting as it is. +language variant + +## The name setting is the name that will show up in the voice settings in the variant combo box. +##The next line is a setting you can change +name Mr_Serious + +##The formant settings +## Formant 0 is used to give a low frequency component to the sounds. +## The three numbers are frequency, strength, and Width, in that order. Please note, the numbers are seperated by a space. +##The next line is a setting you can change +formant 0 100 100 100 + +# Formants 1,2, and 3 are the standard three formants which define vowels. +##The next 3 lines are settings you can change +formant 1 100 100 100 +formant 2 100 100 100 +formant 3 87 100 100 + +# Formants 4,5 are higher than F3. They affect the quality of the voice. +##The next 2 lines are settings that you can change. +formant 4 100 100 100 +formant 5 100 100 100 + +## Formants 6, 7, and 8 are weak, high frequency, additions to vowels to give a clearer sound. +##The next 3 lines are settings that you can change. +formant 6 100 100 100 +formant 7 100 100 100 +formant 8 100 100 100 + +##Intonation affects the rise and fall of the voice +## The settings are 1 default, 2 less intonation, 3 less intonation and commas do not raise the pitch, 4 the pitch rises at the end of a sentence rather than falling. +##The next line is a setting you can change. +intonation 1 + +# Setting the pitch range. The first number gives a base pitch to the voice (value in Hertz). The second number controls the range of pitches used by the voice. Setting it equal +# to the first number will give a monotone sounding voice. The default values are 82 and 118. +pitch 82 118 +## The tone setting. The first number on the setting line, 600, is the frequency setting for the amount of bass in the voice. +## The second number on the tone line is the volume of the bass frequency. You can set it from 0 to 255, 0 being the least amount, 255 being the most. +##The third number on the tone line, 1200, is the mid range frequency. The fourth number on the line is the setting to change the volume of the mid range frequency. +##0 being the least amount and 255 being the maximum. +## The fifth number on the tone line, 2000, is the treble frequency. The sixth number is the volume of the treble frequency. 0 is the minimum and 255 is the maximum. +## You will notice that all 3 frequencies are set to 255. +##The next line is a setting that you can change. +tone 600 255 1200 255 2000 255 +##This file does not include all of the settings that can be used to modify an E Speak voice. It is intended to get you familiar with what the settings do. +##However, you can go to http://espeak.sourceforge.net/voices.html and read further information about other settings that can be added and changed. I hope this helps, and Have fun. diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Nguyen b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Nguyen new file mode 100644 index 0000000..50c72fc --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Nguyen @@ -0,0 +1,16 @@ +language variant +name Nguyen + +pitch 95 175 + +formant 0 100 125 100 +formant 1 96 90 80 +formant 2 97 70 90 +formant 3 97 60 90 +formant 4 97 60 90 +formant 5 75 50 90 +formant 6 90 50 100 +formant 7 100 50 100 +formant 8 100 50 100 + +tone 100 200 600 150 800 100 2400 80 3600 95 5400 100 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Reed b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Reed new file mode 100644 index 0000000..1ccdf5d --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Reed @@ -0,0 +1,15 @@ +language variant +name Reed +klatt 6 +consonants 85 85 +voicing 130 +breath 45 + +pitch 85 135 + +formant 1 72 100 90 90 +formant 2 83 100 75 180 +formant 3 98 100 100 90 +formant 4 98 100 90 +formant 5 100 100 90 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/RicishayMax b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/RicishayMax new file mode 100644 index 0000000..b269b64 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/RicishayMax @@ -0,0 +1,16 @@ +language variant +name RicishayMax +echo 100 10000 + +formant 0 90 120 100 +formant 1 100 100 75 +formant 2 100 100 75 +formant 3 100 80 75 +formant 4 100 80 75 +formant 5 100 80 75 +formant 6 100 0 75 +formant 7 100 0 75 +formant 8 100 0 75 + + + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/RicishayMax2 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/RicishayMax2 new file mode 100644 index 0000000..6585134 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/RicishayMax2 @@ -0,0 +1,25 @@ +language variant +name RicishayMax2 +echo 150 500 + +formant 0 90 120 100 +formant 1 100 100 75 +formant 2 100 100 75 +formant 3 100 80 75 +formant 4 100 80 75 +formant 5 100 80 75 +formant 6 100 0 75 +formant 7 100 0 75 +formant 8 100 0 75 + + +roughness 5 + +intonation 10 +voicing 150 +consonants 110 120 +stressLength 0 1 2 3 4 5 6 7 +stressAdd 130 140 140 100 0 0 130 160 +stressAmp 16 16 24 24 16 16 20 24 + +tone 100 255 600 70 1200 22 2000 66 3000 12 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/RicishayMax3 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/RicishayMax3 new file mode 100644 index 0000000..f481002 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/RicishayMax3 @@ -0,0 +1,25 @@ +language variant +name RicishayMax3 +echo 200 500 + +formant 0 90 120 100 +formant 1 100 100 75 +formant 2 100 100 75 +formant 3 100 80 75 +formant 4 100 80 75 +formant 5 100 80 75 +formant 6 100 0 75 +formant 7 100 0 75 +formant 8 100 0 75 + + +roughness 5 + +intonation 10 +voicing 150 +consonants 110 120 +stressLength 0 1 2 3 4 5 6 7 +stressAdd 130 140 140 100 0 0 130 160 +stressAmp 16 16 24 24 16 16 20 24 + +tone 100 255 600 70 1200 22 2000 66 3000 12 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Storm b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Storm new file mode 100644 index 0000000..d914900 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Storm @@ -0,0 +1,23 @@ +language variant +language en-us +name Storm +gender male +formant 0 100 100 100 +formant 1 95 95 95 +formant 2 95 95 95 +formant 3 95 95 95 +formant 4 70 70 70 +formant 5 70 70 70 +formant 6 25 25 25 +formant 7 25 25 25 +formant 8 25 25 25 +breath 0 0 0 0 0 0 0 0 +consonants 100 +echo 0 0 +flutter 0 +intonation 3 +pitch 60 100 +roughness 0 +stressAdd 5 5 3 3 0 0 -15 -15 +tone 500 255 1500 255 2500 255 +voicing 100 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Tweaky b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Tweaky new file mode 100644 index 0000000..09f60af --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/Tweaky @@ -0,0 +1,50 @@ +##Please note the 2 number signs, or pound signs in this file are for comments to help you to understand what the settings are and how to set them. +## Language sets the language of your voice. This setting is required for every voice that you make. +##The next line is a setting you can change. However if you don't know the language codes it may be best to leave the setting as it is. +language variant + +## The name setting is the name that will show up in the voice settings in the variant combo box. +##The next line is a setting you can change +name Tweaky + +##The formant settings +## Formant 0 is used to give a low frequency component to the sounds. +## The three numbers are frequency, strength, and Width, in that order. Please note, the numbers are seperated by a space. +##The next line is a setting you can change +formant 0 100 100 100 + +# Formants 1,2, and 3 are the standard three formants which define vowels. +##The next 3 lines are settings you can change +formant 1 100 100 100 +formant 2 100 100 100 +formant 3 200 100 100 + +# Formants 4,5 are higher than F3. They affect the quality of the voice. +##The next 2 lines are settings that you can change. +formant 4 100 100 100 +formant 5 100 100 100 + +## Formants 6, 7, and 8 are weak, high frequency, additions to vowels to give a clearer sound. +##The next 3 lines are settings that you can change. +formant 6 100 100 100 +formant 7 100 100 100 +formant 8 100 100 100 + +##Intonation affects the rise and fall of the voice +## The settings are 1 default, 2 less intonation, 3 less intonation and commas do not raise the pitch, 4 the pitch rises at the end of a sentence rather than falling. +##The next line is a setting you can change. +intonation 1 + +# Setting the pitch range. The first number gives a base pitch to the voice (value in Hertz). The second number controls the range of pitches used by the voice. Setting it equal +# to the first number will give a monotone sounding voice. The default values are 82 and 118. +pitch 82 118 +## The tone setting. The first number on the setting line, 600, is the frequency setting for the amount of bass in the voice. +## The second number on the tone line is the volume of the bass frequency. You can set it from 0 to 255, 0 being the least amount, 255 being the most. +##The third number on the tone line, 1200, is the mid range frequency. The fourth number on the line is the setting to change the volume of the mid range frequency. +##0 being the least amount and 255 being the maximum. +## The fifth number on the tone line, 2000, is the treble frequency. The sixth number is the volume of the treble frequency. 0 is the minimum and 255 is the maximum. +## You will notice that all 3 frequencies are set to 255. +##The next line is a setting that you can change. +tone 600 255 1200 255 2000 255 +##This file does not include all of the settings that can be used to modify an E Speak voice. It is intended to get you familiar with what the settings do. +##However, you can go to http://espeak.sourceforge.net/voices.html and read further information about other settings that can be added and changed. I hope this helps, and Have fun. diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/UniRobot b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/UniRobot new file mode 100644 index 0000000..be92b45 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/UniRobot @@ -0,0 +1,19 @@ +language variant +name UniversalRobot +gender male +klatt 4 +pitch 100 160 +echo 10 10000 +formant 1 75 120 135 +formant 2 90 50 140 +formant 3 70 85 95 +formant 4 150 60 80 +formant 5 100 85 80 +formant 6 112 100 80 +formant 7 110 95 100 +formant 8 105 110 100 +consonants 125 100 +tone 530 250 770 100 215 225 +stressLength 0 1 2 3 4 5 6 7 +stressAdd 120 130 130 90 0 0 120 120 +stressAmp 16 16 24 24 16 16 20 24 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/adam b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/adam new file mode 100644 index 0000000..a9f3d2a --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/adam @@ -0,0 +1,6 @@ +language variant +name Adam +klatt 6 +consonants 85 85 + +formant 1 100 100 130 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/anika b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/anika new file mode 100644 index 0000000..2413c90 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/anika @@ -0,0 +1,25 @@ +language variant +name anika +gender female +pitch 200 300 +flutter 6 +stressAmp 20 18 20 20 20 22 22 22 + +roughness 0 + +formant 0 105 200 140 +formant 1 95 150 120 +formant 2 100 120 140 +formant 3 95 95 140 +formant 4 120 120 110 +formant 5 120 120 110 +formant 6 110 60 65 +formant 7 100 0 100 +formant 8 100 0 100 +intonation 10 +voicing 30 +consonants 60 40 +stressLength 0 1 2 3 4 5 6 7 +stressAdd 130 140 140 100 0 0 130 160 +stressAmp 16 16 24 24 16 16 20 24 +tone 100 255 600 70 1200 22 2000 66 3000 12 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/anikaRobot b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/anikaRobot new file mode 100644 index 0000000..791d2bb --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/anikaRobot @@ -0,0 +1,26 @@ +language variant +name anikaRobot +gender female +pitch 200 300 +flutter 1 +stressAmp 20 18 20 20 20 22 22 22 +echo 10 10000 + +roughness 0 + +formant 0 105 200 140 +formant 1 95 150 120 +formant 2 100 120 140 +formant 3 95 95 140 +formant 4 120 120 110 +formant 5 120 120 110 +formant 6 110 60 65 +formant 7 100 0 100 +formant 8 100 0 100 +intonation 10 +voicing 30 +consonants 60 40 +stressLength 0 1 2 3 4 5 6 7 +stressAdd 130 140 140 100 0 0 130 160 +stressAmp 16 16 24 24 16 16 20 24 +tone 100 255 600 70 1200 22 2000 66 3000 12 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/announcer b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/announcer new file mode 100644 index 0000000..71e0795 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/announcer @@ -0,0 +1,17 @@ +name Half-LifeAnnouncementSystem +language variant +pitch 37 83 +klatt 4 + +formant 1 88 100 100 0 +formant 2 96 100 100 0 +formant 3 98 100 100 0 +formant 4 96 100 100 0 +formant 5 100 100 100 0 +formant 6 100 100 100 0 +formant 7 100 100 100 0 +formant 8 100 100 100 0 + +voicing 70 +consonants 70 70 +echo 154 26 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/antonio b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/antonio new file mode 100644 index 0000000..a41b681 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/antonio @@ -0,0 +1,21 @@ +language variant +name Antonio +gender male + +pitch 82 128 +roughness 0 + +formant 0 100 150 90 +formant 1 90 130 90 +formant 2 95 120 80 +formant 3 100 50 80 +formant 4 100 40 80 +formant 5 90 70 80 +formant 6 0 0 0 +formant 7 100 100 100 +formant 8 100 100 100 +voicing 150 +tone 600 255 1200 255 2000 80 +intonation 3 +stressLength 0 1 2 3 4 5 6 7 +stressAdd 130 140 140 100 0 0 130 160 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/aunty b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/aunty new file mode 100644 index 0000000..0c3851f --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/aunty @@ -0,0 +1,19 @@ +language variant +name Auntie +gender female +pitch 204 176 +flutter 12 + +formant 0 88 85 154 +formant 1 115 80 160 -20 +formant 2 130 75 150 -200 +formant 3 123 75 150 +formant 4 125 80 150 +formant 5 125 80 150 +formant 6 110 80 150 +formant 7 110 75 150 +formant 8 110 75 150 + +stressAdd -20 -20 -20 -20 0 0 20 120 +stressAmp 18 16 20 20 20 20 20 20 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/belinda b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/belinda new file mode 100644 index 0000000..9a9e42b --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/belinda @@ -0,0 +1,20 @@ +language variant +name Belinda +gender female + +pitch 200 247 +flutter 3 + +formant 0 88 85 154 +formant 1 135 58 169 -30 +formant 2 120 70 150 -260 +formant 3 120 39 150 +formant 4 125 57 80 +formant 5 125 80 150 +formant 6 110 80 150 +formant 7 110 75 150 +formant 8 110 75 150 + +stressAdd -20 -20 -20 -20 0 3 20 12 +stressAmp 18 16 20 20 10 20 27 20 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/benjamin b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/benjamin new file mode 100644 index 0000000..c300643 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/benjamin @@ -0,0 +1,11 @@ +language variant +name Benjamin +klatt 6 +consonants 70 70 + +formant 1 101 100 130 +formant 2 102 100 100 +formant 3 100 100 100 +formant 4 100 100 100 470 +formant 5 100 100 100 350 +formant 6 100 100 100 100 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/boris b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/boris new file mode 100644 index 0000000..2ee4d6f --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/boris @@ -0,0 +1,15 @@ +language variant +name Boris + +formant 0 47 120 100 +formant 1 100 90 75 +formant 2 104 100 75 +formant 3 57 80 75 +formant 4 104 80 75 +formant 5 107 80 75 +formant 6 68 0 75 +formant 7 105 0 75 +formant 8 105 0 75 + + + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/caleb b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/caleb new file mode 100644 index 0000000..9c8a5b9 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/caleb @@ -0,0 +1,5 @@ +language variant +name Caleb +klatt 6 +breath 100 +voicing 0 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/croak b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/croak new file mode 100644 index 0000000..ae76a4c --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/croak @@ -0,0 +1,11 @@ +language variant +name croak +gender male 70 + +pitch 85 117 +flutter 20 + +formant 0 100 80 110 + + + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/david b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/david new file mode 100644 index 0000000..7dc75dd --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/david @@ -0,0 +1,8 @@ +language variant +name David +klatt 6 +pitch 62 89 + +formant 1 75 100 100 +formant 2 85 100 100 +formant 3 85 100 100 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/ed b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/ed new file mode 100644 index 0000000..7f293fc --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/ed @@ -0,0 +1,17 @@ +language variant +name Ed + +pitch 90 145 + +formant 0 110 120 200 5 +formant 1 102 100 80 +formant 2 101 120 100 +formant 3 100 80 75 +formant 4 150 30 80 +formant 5 95 95 155 +formant 6 167 100 75 +formant 7 100 200 75 +formant 8 60 200 95 +consonants 55 80 +voicing 100 +tone 650 250 1000 130 240 255 \ No newline at end of file diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/edward b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/edward new file mode 100644 index 0000000..303f505 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/edward @@ -0,0 +1,10 @@ +language variant +name Edward +klatt 5 +voicing 100 +consonants 70 80 + +formant 1 92 100 130 +formant 2 103 100 80 +formant 3 103 100 70 +formant 4 114 100 60 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/edward2 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/edward2 new file mode 100644 index 0000000..abee6aa --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/edward2 @@ -0,0 +1,10 @@ +language variant +name Edward2 +klatt 6 +voicing 100 +consonants 70 80 + +formant 1 92 100 130 +formant 2 103 100 80 +formant 3 103 100 70 +formant 4 114 100 60 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/f1 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/f1 new file mode 100644 index 0000000..8f03a73 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/f1 @@ -0,0 +1,18 @@ +language variant +name female1 +gender female 70 + +pitch 140 200 +flutter 8 +roughness 4 +formant 0 115 80 150 +formant 1 120 80 180 +formant 2 100 70 150 150 +formant 3 115 70 150 +formant 4 110 80 150 +formant 5 110 90 150 +formant 6 105 80 150 +formant 7 110 70 150 +formant 8 110 70 150 + +stressAdd -10 -10 -20 -20 0 0 40 60 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/f2 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/f2 new file mode 100644 index 0000000..4122d96 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/f2 @@ -0,0 +1,21 @@ +language variant +name female2 +gender female + +pitch 142 220 +roughness 3 + +formant 0 105 80 150 +formant 1 110 80 160 +formant 2 110 70 150 +formant 3 110 70 150 +formant 4 115 80 150 +formant 5 115 80 150 +formant 6 110 70 150 +formant 7 110 70 150 +formant 8 110 70 150 + +stressAdd 0 0 -10 -10 0 0 10 40 +breath 0 2 3 3 3 3 3 2 +echo 140 10 +consonants 125 125 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/f3 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/f3 new file mode 100644 index 0000000..92a1582 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/f3 @@ -0,0 +1,22 @@ +language variant +name female3 +gender female + +pitch 140 240 +formant 0 105 80 150 +formant 1 120 75 150 -50 +formant 2 135 70 150 -250 +formant 3 125 80 150 +formant 4 125 80 150 +formant 5 125 80 150 +formant 6 120 70 150 +formant 7 110 70 150 +formant 8 110 70 150 + +stressAmp 18 18 20 20 20 20 20 20 +//breath 0 2 4 4 4 4 4 4 +breath 0 2 3 3 3 3 3 2 +echo 120 10 +roughness 4 + + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/f4 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/f4 new file mode 100644 index 0000000..52c5ac9 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/f4 @@ -0,0 +1,18 @@ +language variant +name female4 +gender female + +echo 130 15 +pitch 142 200 +formant 0 120 80 150 +formant 1 115 80 160 -20 +formant 2 130 75 150 -200 +formant 3 123 75 150 +formant 4 125 80 150 +formant 5 125 80 150 +formant 6 110 80 150 +formant 7 110 75 150 +formant 8 110 75 150 + +stressAdd -20 -20 -20 -20 0 0 20 120 +stressAmp 18 16 20 20 20 20 20 20 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/f5 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/f5 new file mode 100644 index 0000000..7fa4f88 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/f5 @@ -0,0 +1,23 @@ +language variant +name female5 +gender female + +pitch 160 228 +roughness 0 + +formant 0 105 80 150 +formant 1 110 80 160 +formant 2 110 70 150 +formant 3 110 70 150 +formant 4 115 80 200 +formant 5 115 80 100 +formant 6 110 70 150 +formant 7 110 70 100 +formant 8 110 70 150 + +stressAdd 0 0 -10 -10 0 0 10 40 +breath 0 4 6 6 6 6 0 10 +echo 140 10 +voicing 75 +consonants 150 150 +breathw 150 150 200 200 400 400 600 600 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/fast b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/fast new file mode 100644 index 0000000..a2c3da2 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/fast @@ -0,0 +1,7 @@ +language variant +name fast_test + +// Try decreasing these values to make eSpeak's fastest speed faster. +// This is currently unstable. + +fast_test2 15 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/grandma b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/grandma new file mode 100644 index 0000000..395ebe2 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/grandma @@ -0,0 +1,17 @@ +language variant + +name grandma +gender female 90 +pitch 120 230 + +flutter 20 +formant 0 105 150 150 +formant 1 100 80 100 +formant 2 105 105 105 +formant 3 80 80 80 +formant 4 60 60 60 +formant 5 90 90 90 +formant 6 10 10 10 +formant 7 10 10 10 +formant 8 20 20 20 +voicing 50 \ No newline at end of file diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/grandpa b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/grandpa new file mode 100644 index 0000000..41a870c --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/grandpa @@ -0,0 +1,14 @@ +language variant +name grandpa +pitch 80 120 +flutter 20 +formant 0 100 100 100 +formant 1 100 100 100 +formant 2 100 100 100 +formant 3 100 100 100 +formant 4 100 100 100 +formant 5 100 100 100 +formant 6 10 10 10 +formant 7 10 10 10 +formant 8 10 10 10 +intonation 1 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/gustave b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/gustave new file mode 100644 index 0000000..ce1d71b --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/gustave @@ -0,0 +1,17 @@ +language variant +name Gustave + +pitch 80 123 + +formant 0 85 141 135 +formant 1 77 131 45 +formant 2 92 70 55 +formant 3 59 50 65 +formant 4 69 65 65 +formant 5 79 60 75 +formant 6 89 60 75 +formant 7 99 0 100 +formant 8 109 0 100 +voicing 135 +consonants 115 120 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/ian b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/ian new file mode 100644 index 0000000..4b7d4d2 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/ian @@ -0,0 +1,51 @@ +##Please note the 2 number signs, or pound signs in this file are for comments to help you to understand what the settings are and how to set them. +## Language sets the language of your voice. This setting is required for every voice that you make. +##The next line is a setting you can change. However if you don't know the language codes it may be best to leave the setting as it is. +language variant + +## The name setting is the name that will show up in the voice settings in the variant combo box. +##The next line is a setting you can change +name Ian + +##The formant settings +## Formant 0 is used to give a low frequency component to the sounds. +## The three numbers are frequency, strength, and Width, in that order. Please note, the numbers are seperated by a space. +##The next line is a setting you can change +formant 0 20 120 50 + +# Formants 1,2, and 3 are the standard three formants which define vowels. +##The next 3 lines are settings you can change +formant 1 80 80 80 +formant 2 80 80 80 +formant 3 80 80 80 + +# Formants 4,5 are higher than F3. They affect the quality of the voice. +##The next 2 lines are settings that you can change. +formant 4 50 50 50 +formant 5 50 50 50 + +## Formants 6, 7, and 8 are weak, high frequency, additions to vowels to give a clearer sound. +##The next 3 lines are settings that you can change. +formant 6 100 100 100 +formant 7 200 50 200 +formant 8 200 50 200 + +##Intonation affects the rise and fall of the voice +## The settings are 1 default, 2 less intonation, 3 less intonation and commas do not raise the pitch, 4 the pitch rises at the end of a sentence rather than falling. +##The next line is a setting you can change. +intonation 2 + +# Setting the pitch range. The first number gives a base pitch to the voice (value in Hertz). The second number controls the range of pitches used by the voice. +# Setting it equal to the first number will give a monotone sounding voice. The default values are 82 and 118. +pitch 69 96 + +## The tone setting. The first number on the setting line, 600, is the frequency setting for the amount of bass in the voice. +##The second number on the tone line is the volume of the bass frequency. You can set it from 0 to 255, 0 being the least amount, 255 being the most. +##The third number on the tone line, 1200, is the mid range frequency. The fourth number on the line is the setting to change the volume of the mid range frequency. +##0 being the least amount and 255 being the maximum. +## The fifth number on the tone line, 2000, is the treble frequency. The sixth number is the volume of the treble frequency. 0 is the minimum and 255 is the maximum. +## You will notice that all 3 frequencies are set to 255. +##The next line is a setting that you can change. +tone 1000 127 1200 127 2000 127 +##This file does not include all of the settings that can be used to modify an E Speak voice. It is intended to get you familiar with what the settings do. +##However, you can go to http://espeak.sourceforge.net/voices.html and read further information about other settings that can be added and changed. I hope this helps, and Have fun. diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/iven b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/iven new file mode 100644 index 0000000..3b8c120 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/iven @@ -0,0 +1,14 @@ +language variant +name Iven +pitch 74 118 +formant 0 52 133 88 +formant 1 87 82 76 +formant 2 94 56 42 +formant 3 93 52 130 +formant 4 110 76 65 +formant 5 102 45 20 +formant 6 40 50 50 +formant 7 60 50 60 +formant 8 100 50 40 +voicing 530 +tone 600 255 1200 255 2000 160 \ No newline at end of file diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/iven2 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/iven2 new file mode 100644 index 0000000..e61fd73 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/iven2 @@ -0,0 +1,15 @@ +language variant +name Iven2 +pitch 74 118 +formant 0 52 133 88 +formant 1 87 82 76 +formant 2 94 56 42 +formant 3 93 52 130 +formant 4 110 76 65 +formant 5 102 45 20 +formant 6 40 50 50 +formant 7 60 50 60 +formant 8 100 50 40 +voicing 220 +consonants 28 42 +tone 600 255 1200 255 2000 150 \ No newline at end of file diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/iven3 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/iven3 new file mode 100644 index 0000000..a58e745 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/iven3 @@ -0,0 +1,14 @@ +language variant +name Iven3 +pitch 74 118 +formant 0 52 133 88 +formant 1 87 82 76 +formant 2 94 56 42 +formant 3 93 52 130 +formant 4 110 76 65 +formant 5 102 45 20 +formant 6 40 50 50 +formant 7 60 50 60 +formant 8 100 50 40 +voicing 165 +tone 600 255 1200 255 2000 160 \ No newline at end of file diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/iven4 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/iven4 new file mode 100644 index 0000000..43084c1 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/iven4 @@ -0,0 +1,14 @@ +language variant +name Iven4 +pitch 74 118 +formant 0 52 133 88 +formant 1 87 82 76 +formant 2 94 56 42 +formant 3 93 52 130 +formant 4 110 76 65 +formant 5 102 45 20 +formant 6 40 50 50 +formant 7 60 50 60 +formant 8 100 50 40 +voicing 165 +tone 600 170 1200 100 2000 40 \ No newline at end of file diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/john b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/john new file mode 100644 index 0000000..97fbf74 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/john @@ -0,0 +1,50 @@ +##Please note the 2 number signs, or pound signs in this file are for comments to help you to understand what the settings are and how to set them. +## Language sets the language of your voice. This setting is required for every voice that you make. +##The next line is a setting you can change. However if you don't know the language codes it may be best to leave the setting as it is. +language variant + +## The name setting is the name that will show up in the voice settings in the variant combo box. +##The next line is a setting you can change +name John + +##The formant settings +## Formant 0 is used to give a low frequency component to the sounds. +## The three numbers are frequency, strength, and Width, in that order. Please note, the numbers are seperated by a space. +##The next line is a setting you can change +formant 0 100 100 100 + +# Formants 1,2, and 3 are the standard three formants which define vowels. +##The next 3 lines are settings you can change +formant 1 100 100 100 +formant 2 100 100 100 +formant 3 100 100 100 + +# Formants 4,5 are higher than F3. They affect the quality of the voice. +##The next 2 lines are settings that you can change. +formant 4 100 100 100 +formant 5 100 100 100 + +## Formants 6, 7, and 8 are weak, high frequency, additions to vowels to give a clearer sound. +##The next 3 lines are settings that you can change. +formant 6 100 100 100 +formant 7 100 100 100 +formant 8 100 100 100 + +##Intonation affects the rise and fall of the voice +## The settings are 1 default, 2 less intonation, 3 less intonation and commas do not raise the pitch, 4 the pitch rises at the end of a sentence rather than falling. +##The next line is a setting you can change. +intonation 1 + +# Setting the pitch range. The first number gives a base pitch to the voice (value in Hertz). The second number controls the range of pitches used by the voice. +# Setting it equal to the first number will give a monotone sounding voice. The default values are 82 and 118. +pitch 82 118 +## The tone setting. The first number on the setting line, 600, is the frequency setting for the amount of bass in the voice. +##The second number on the tone line is the volume of the bass frequency. You can set it from 0 to 255, 0 being the least amount, 255 being the most. +##The third number on the tone line, 1200, is the mid range frequency. The fourth number on the line is the setting to change the volume of the mid range frequency. +##0 being the least amount and 255 being the maximum. +## The fifth number on the tone line, 2000, is the treble frequency. The sixth number is the volume of the treble frequency. 0 is the minimum and 255 is the maximum. +## You will notice that all 3 frequencies are set to 255. +##The next line is a setting that you can change. +tone 600 255 1200 255 2000 255 +##This file does not include all of the settings that can be used to modify an E Speak voice. It is intended to get you familiar with what the settings do. +##However, you can go to http://espeak.sourceforge.net/voices.html and read further information about other settings that can be added and changed. I hope this helps, and Have fun. diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/kaukovalta b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/kaukovalta new file mode 100644 index 0000000..3f706e4 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/kaukovalta @@ -0,0 +1,16 @@ +language variant +name Kaukovalta +formant 0 80 80 100 +formant 1 40 80 100 +formant 2 70 100 130 +formant 3 80 100 60 +formant 4 70 90 100 +formant 5 70 90 100 +formant 6 70 100 90 + formant 7 100 90 110 +formant 8 100 95 100 +pitch 70 120 +tone 100 130 800 130 2000 130 +consonants 70 70 +roughness 4 + \ No newline at end of file diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/klatt b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/klatt new file mode 100644 index 0000000..b739a86 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/klatt @@ -0,0 +1,4 @@ +language variant +name klatt +klatt 1 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/klatt2 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/klatt2 new file mode 100644 index 0000000..01477be --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/klatt2 @@ -0,0 +1,4 @@ +language variant +name klatt2 +klatt 2 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/klatt3 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/klatt3 new file mode 100644 index 0000000..b1a874b --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/klatt3 @@ -0,0 +1,4 @@ +language variant +name klatt3 +klatt 3 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/klatt4 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/klatt4 new file mode 100644 index 0000000..6527808 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/klatt4 @@ -0,0 +1,4 @@ +language variant +name klatt4 +klatt 4 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/klatt5 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/klatt5 new file mode 100644 index 0000000..9d831fb --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/klatt5 @@ -0,0 +1,4 @@ +language variant +name klatt5 +klatt 5 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/klatt6 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/klatt6 new file mode 100644 index 0000000..7656ab3 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/klatt6 @@ -0,0 +1,4 @@ +language variant +name klatt6 +klatt 6 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/linda b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/linda new file mode 100644 index 0000000..a56a8b0 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/linda @@ -0,0 +1,20 @@ +language variant +name Linda +gender female + +#echo 130 15 +pitch 200 247 +flutter 3 +formant 0 88 85 154 +formant 1 135 58 169 -30 +formant 2 131 75 152 -260 +formant 3 123 75 150 +formant 4 125 80 150 +formant 5 125 80 150 +formant 6 110 80 150 +formant 7 110 75 150 +formant 8 110 75 150 + +stressAdd -20 -20 -20 -20 0 3 20 120 +stressAmp 18 16 20 20 20 20 27 20 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/m1 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/m1 new file mode 100644 index 0000000..4cc9a00 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/m1 @@ -0,0 +1,20 @@ +language variant +name male1 +gender male 70 + +pitch 75 109 +flutter 5 +roughness 4 +consonants 80 100 + +formant 0 98 100 100 +formant 1 97 100 100 +formant 2 97 95 100 +formant 3 97 95 100 +formant 4 97 85 100 +formant 5 105 80 100 +formant 6 95 80 100 +formant 7 100 100 100 +formant 8 100 100 100 + +//stressAdd -10 -10 -20 -20 0 0 40 70 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/m2 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/m2 new file mode 100644 index 0000000..c234f46 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/m2 @@ -0,0 +1,15 @@ +language variant +name male2 +gender male + +pitch 88 115 +echo 130 15 +formant 0 100 80 120 +formant 1 90 85 120 +formant 2 110 85 120 +formant 3 105 90 120 +formant 4 100 90 120 +formant 5 100 90 120 +formant 6 100 90 120 +formant 7 100 90 120 +formant 8 100 90 120 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/m3 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/m3 new file mode 100644 index 0000000..00479dc --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/m3 @@ -0,0 +1,17 @@ +language variant +name male3 +gender male + +pitch 80 122 +formant 0 100 100 100 +formant 1 96 97 100 +formant 2 96 97 100 +formant 3 96 103 100 +formant 4 95 103 100 +formant 5 95 103 100 +formant 6 100 100 100 +formant 7 100 100 100 +formant 8 100 100 100 + +consonants 100 +stressAdd 10 10 0 0 0 0 -30 -30 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/m4 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/m4 new file mode 100644 index 0000000..7199341 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/m4 @@ -0,0 +1,17 @@ +language variant +name male4 +gender male + +pitch 70 110 + +formant 0 103 100 100 +formant 1 103 100 100 +formant 2 103 100 100 +formant 3 103 100 100 +formant 4 106 100 100 +formant 5 106 100 100 +formant 6 106 100 100 +formant 7 103 100 100 +formant 8 103 100 100 + +stressAdd -10 -10 -30 -30 0 0 60 90 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/m5 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/m5 new file mode 100644 index 0000000..d258656 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/m5 @@ -0,0 +1,15 @@ +language variant +name male5 +gender male + +formant 0 100 85 130 +formant 1 90 85 130 40 +formant 2 80 85 130 310 +formant 3 105 85 130 +formant 4 105 85 130 +formant 5 105 85 130 +formant 6 105 85 150 +formant 7 105 85 150 +formant 8 105 85 150 + +intonation 2 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/m6 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/m6 new file mode 100644 index 0000000..bd336a9 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/m6 @@ -0,0 +1,13 @@ +language variant +name male6 +gender male + +pitch 82 117 + +formant 0 100 90 120 +formant 1 100 90 140 +formant 2 100 70 140 +formant 3 100 75 140 +formant 4 100 80 140 +formant 5 100 80 140 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/m7 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/m7 new file mode 100644 index 0000000..11b49ed --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/m7 @@ -0,0 +1,17 @@ +language variant +name male7 +gender male + +pitch 75 125 + +formant 0 100 125 100 +formant 1 100 90 80 +formant 2 100 70 90 +formant 3 100 60 90 +formant 4 100 60 90 +formant 5 75 50 90 +formant 6 90 50 100 +formant 7 100 50 100 +formant 8 100 50 100 +voicing 155 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/m8 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/m8 new file mode 100644 index 0000000..c03ca3e --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/m8 @@ -0,0 +1,16 @@ +language variant +name male8 +gender male 50 + +pitch 65 102 +formant 0 100 125 100 +formant 1 96 90 80 +formant 2 97 70 90 +formant 3 97 60 90 +formant 4 97 60 90 +formant 5 100 50 90 +formant 6 90 50 100 +formant 7 100 50 100 +formant 8 100 50 100 + +tone 100 255 600 70 1200 22 2000 66 3000 12 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/marcelo b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/marcelo new file mode 100644 index 0000000..8df9651 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/marcelo @@ -0,0 +1,17 @@ +language variant +name Marcelo + +pitch 65 115 + +formant 0 65 161 35 +formant 1 75 131 65 +formant 2 90 60 40 +formant 3 59 50 55 +formant 4 69 65 35 +formant 5 69 60 25 +formant 6 59 60 35 +formant 7 149 0 10 +formant 8 199 0 90 +voicing 135 +consonants 115 120 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/max b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/max new file mode 100644 index 0000000..17701a8 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/max @@ -0,0 +1,15 @@ +language variant +name Max + +formant 0 90 120 100 +formant 1 100 100 75 +formant 2 100 100 75 +formant 3 100 80 75 +formant 4 100 80 75 +formant 5 100 80 75 +formant 6 100 0 75 +formant 7 100 0 75 +formant 8 100 0 75 + + + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/michel b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/michel new file mode 100644 index 0000000..530ad28 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/michel @@ -0,0 +1,22 @@ +language variant +name Michel +gender male 25 +pitch 82 122 +echo 0 0 +flutter 0 +roughness 0 +stressAmp 20 18 20 20 20 22 22 22 + + +formant 0 105 200 140 +formant 1 95 150 120 +formant 2 100 120 140 +formant 3 95 95 140 +formant 4 30 30 30 -100 +formant 5 90 90 90 +formant 6 110 60 65 +formant 7 100 0 100 +formant 8 100 0 100 +voicing 35 +consonants 60 40 +tone 400 160 1500 100 3000 70 4500 40 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/miguel b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/miguel new file mode 100644 index 0000000..53d71e7 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/miguel @@ -0,0 +1,22 @@ +language variant +name Miguel +gender male 25 +pitch 80 130 +echo 0 0 +flutter 0 +roughness 0 +stressAmp 20 18 20 20 20 22 22 22 + + +formant 0 105 200 140 +formant 1 95 150 120 +formant 2 100 120 140 +formant 3 95 95 140 +formant 4 30 30 30 -100 +formant 5 90 90 90 +formant 6 110 60 65 +formant 7 100 0 100 +formant 8 100 0 100 +voicing 35 +consonants 60 40 +tone 300 240 400 160 1500 100 3000 70 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/mike2 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/mike2 new file mode 100644 index 0000000..2715f4d --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/mike2 @@ -0,0 +1,12 @@ +language variant +name Mike2 +klatt 6 +voicing 170 +pitch 67 107 +formant 1 95 100 100 +formant 2 95 100 100 +formant 3 105 100 100 +formant 4 115 100 100 +formant 5 115 100 100 + +consonants 70 150 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/norbert b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/norbert new file mode 100644 index 0000000..4e9c1fd --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/norbert @@ -0,0 +1,50 @@ +##Please note the 2 number signs, or pound signs in this file are for comments to help you to understand what the settings are and how to set them. +## Language sets the language of your voice. This setting is required for every voice that you make. +##The next line is a setting you can change. However if you don't know the language codes it may be best to leave the setting as it is. +language variant + +## The name setting is the name that will show up in the voice settings in the variant combo box. +##The next line is a setting you can change +name norbert + +##The formant settings +## Formant 0 is used to give a low frequency component to the sounds. +## The three numbers are frequency, strength, and Width, in that order. Please note, the numbers are seperated by a space. +##The next line is a setting you can change +formant 0 100 100 100 + +# Formants 1,2, and 3 are the standard three formants which define vowels. +##The next 3 lines are settings you can change +formant 1 100 100 100 +formant 2 75 50 100 +formant 3 100 100 100 + +# Formants 4,5 are higher than F3. They affect the quality of the voice. +##The next 2 lines are settings that you can change. +formant 4 100 100 100 +formant 5 100 100 100 + +## Formants 6, 7, and 8 are weak, high frequency, additions to vowels to give a clearer sound. +##The next 3 lines are settings that you can change. +formant 6 100 100 100 +formant 7 100 100 100 +formant 8 100 100 100 + +##Intonation affects the rise and fall of the voice +## The settings are 1 default, 2 less intonation, 3 less intonation and commas do not raise the pitch, 4 the pitch rises at the end of a sentence rather than falling. +##The next line is a setting you can change. +intonation 1 + +# Setting the pitch range. The first number gives a base pitch to the voice (value in Hertz). The second number controls the range of pitches used by the voice. Setting it equal +# to the first number will give a monotone sounding voice. The default values are 82 and 118. +pitch 82 118 +## The tone setting. The first number on the setting line, 600, is the frequency setting for the amount of bass in the voice. +## The second number on the tone line is the volume of the bass frequency. You can set it from 0 to 255, 0 being the least amount, 255 being the most. +##The third number on the tone line, 1200, is the mid range frequency. The fourth number on the line is the setting to change the volume of the mid range frequency. +##0 being the least amount and 255 being the maximum. +## The fifth number on the tone line, 2000, is the treble frequency. The sixth number is the volume of the treble frequency. 0 is the minimum and 255 is the maximum. +## You will notice that all 3 frequencies are set to 255. +##The next line is a setting that you can change. +tone 600 255 1000 100 5000 255 +##This file does not include all of the settings that can be used to modify an E Speak voice. It is intended to get you familiar with what the settings do. +##However, you can go to http://espeak.sourceforge.net/voices.html and read further information about other settings that can be added and changed. I hope this helps, and Have fun. diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/pablo b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/pablo new file mode 100644 index 0000000..031e5fe --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/pablo @@ -0,0 +1,52 @@ +##Pleas note the 2 number signs, or pound signs in this file are for comments to help you to understand what the settings are and how to set them. +## Language sets the language of your voice. This setting is required for every voice that you make. +##The next line is a setting you can change. However if you don't know the language codes it may be best to leave the setting as it is. +language variant + +## The name setting is the name that will show up in the voice settings in the variant combo box. +##The next line is a setting you can change +name Pablo + +##The formant settings +## Formant 0 is used to give a low frequency component to the sounds. +## The three numbers are frequency, strength, and Width, in that order. Please note, the numbers are seperated by a space. +##The next line is a setting you can change +formant 0 90 100 90 + +# Formants 1,2, and 3 are the standard three formants which define vowels. +##The next 3 lines are settings you can change +formant 1 95 100 80 +formant 2 97 100 80 +formant 3 98 90 80 + +# Formants 4,5 are higher than F3. They affect the quality of the voice. +##The next 2 lines are settings that you can change. +formant 4 110 100 100 +formant 5 110 100 100 + +## Formants 6, 7, and 8 are weak, high frequency, additions to vowels to give a clearer sound. +##The next 3 lines are settings that you can change. +formant 6 100 100 100 +formant 7 100 100 100 +formant 8 100 100 100 + +##Intonation affects the rise and fall of the voice +## The settings are 1 default, 2 less intonation, 3 less intonation and commas do not raise the pitch, 4 the pitch rises at the end of a sentence rather than falling. +##The next line is a setting you can change. +intonation 3 +echo 30 30 +# Setting the pitch range. The first number gives a base pitch to the voice (value in Hertz). The second number controls the range of pitches used by the voice. Setting it equal +# to the first number will give a monotone sounding voice. The default values are 82 and 118. +pitch 82 130 +## The tone setting. The first number on the setting line, 600, is the frequency setting for the amount of bass in the voice. +## The second number on the tone line is the volume of the bass frequency. You can set it from 0 to 255, 0 being the least amount, 255 being the most. +##The third number on the tone line, 1200, is the mid range frequency. The fourth number on the line is the setting to change the volume of the mid range frequency. +## 0 being the least amount and 255 being the maximum. +## The fifth number on the tone line, 2000, is the treble frequency. The sixth number is the volume of the treble frequency. 0 is the minimum and 255 is the maximum. +## You will notice that all 3 frequencies are set to 255. +##The next line is a setting that you can change. +tone 600 255 1200 200 2000 255 + +##This file does not include all of the settings that can be used to modify an E Speak voice. It is intended to get you familiar with what the settings do. +#However, you can go to http://espeak.sourceforge.net/voices.html and read further information about other settings that can be added and changed. I hope this helps, and Have fun. + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/paul b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/paul new file mode 100644 index 0000000..9015cd3 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/paul @@ -0,0 +1,17 @@ +language variant +name Paul + +pitch 70 100 + +formant 0 90 120 100 +formant 1 103 100 75 +formant 2 98 100 75 +formant 3 100 80 75 +formant 4 102 30 100 +formant 5 100 80 100 +formant 6 100 80 75 +formant 7 100 0 75 +formant 8 100 60 75 +consonants 90 60 +voicing 230 +tone 420 255 1300 130 4000 100 \ No newline at end of file diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/pedro b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/pedro new file mode 100644 index 0000000..5cc34db --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/pedro @@ -0,0 +1,23 @@ +language variant + +name Pedro + +formant 0 100 150 100 + +formant 1 95 100 80 +formant 2 95 100 80 +formant 3 100 100 90 + +formant 4 100 100 100 +formant 5 100 100 100 + +formant 6 100 100 100 +formant 7 100 100 100 +formant 8 100 100 100 + +intonation 3 + +pitch 82 118 +tone 600 255 1200 255 2000 255 +stressLength 0 1 2 3 4 5 6 7 +stressAdd 130 140 140 100 0 0 130 160 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/quincy b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/quincy new file mode 100644 index 0000000..b0b3a57 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/quincy @@ -0,0 +1,20 @@ +language variant +name Quincy + +pitch 67 100 + +formant 0 85 108 106 3 +formant 1 97 110 56 +formant 2 96 80 60 +formant 3 101 50 50 +formant 4 110 33 55 +formant 5 110 22 65 +formant 6 77 60 60 65 +formant 7 66 0 100 +formant 8 100 0 100 +voicing 99 +consonants 66 90 + +roughness 0 +tone 600 170 1200 100 2000 70 +stressAmp 16 16 24 20 20 16 28 24 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/rob b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/rob new file mode 100644 index 0000000..b9f04a7 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/rob @@ -0,0 +1,17 @@ +language variant +name Rob + +pitch 50 130 +formant 0 100 100 100 +formant 1 95 100 60 +formant 2 97 90 50 +formant 3 101 70 50 +formant 4 110 65 55 +formant 5 110 70 65 +formant 6 110 70 65 +formant 7 0 0 0 +formant 8 0 0 0 + +voicing 115 +consonants 110 120 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/robert b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/robert new file mode 100644 index 0000000..94b7209 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/robert @@ -0,0 +1,17 @@ +language variant +name Robert + +pitch 65 115 + +formant 0 85 108 100 +formant 1 95 110 60 +formant 2 97 90 50 +formant 3 101 50 50 +formant 4 110 65 55 +formant 5 110 60 65 +formant 6 110 60 65 +formant 7 100 0 100 +formant 8 100 0 100 +voicing 115 +consonants 110 120 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/robosoft b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/robosoft new file mode 100644 index 0000000..0668d9a --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/robosoft @@ -0,0 +1,26 @@ +language variant +name Robosoft +echo 30 1000 +klatt 5 + +pitch 60 90 +formant 0 100 125 100 +formant 1 96 90 80 +formant 2 97 70 90 +formant 3 97 60 90 +formant 4 97 60 90 +formant 5 100 50 90 +formant 6 90 50 100 +formant 7 100 50 100 +formant 8 100 50 100 + +roughness 50 + +intonation 0 +voicing 80 +consonants 110 120 +stressLength 0 1 2 3 4 5 6 7 +stressAdd 130 140 140 100 0 0 130 160 +stressAmp 16 16 24 24 16 16 20 24 + +tone 100 255 600 70 1200 22 2000 66 3000 12 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/robosoft2 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/robosoft2 new file mode 100644 index 0000000..1695414 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/robosoft2 @@ -0,0 +1,26 @@ +language variant +name Robosoft2 +echo 10 600 +klatt 4 + +pitch 70 100 +formant 0 100 125 100 +formant 1 96 90 80 +formant 2 97 70 90 +formant 3 97 60 90 +formant 4 97 60 90 +formant 5 100 50 90 +formant 6 90 50 100 +formant 7 100 50 100 +formant 8 100 50 100 + +roughness 75 + +intonation -25 +voicing 80 +consonants 110 120 +stressLength 0 1 2 3 4 5 6 7 +stressAdd 130 140 140 100 0 0 130 160 +stressAmp 16 16 24 24 16 16 20 24 + +tone 100 255 600 70 1200 22 2000 66 3000 12 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/robosoft3 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/robosoft3 new file mode 100644 index 0000000..15bbe34 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/robosoft3 @@ -0,0 +1,26 @@ +language variant +name Robosoft3 +echo 10 10000 +klatt 4 + +pitch 75 115 +formant 0 100 125 100 +formant 1 96 90 80 +formant 2 97 70 90 +formant 3 97 60 90 +formant 4 97 60 90 +formant 5 100 50 90 +formant 6 90 50 100 +formant 7 100 50 100 +formant 8 100 50 100 + +roughness 5 + +intonation 10 +voicing 150 +consonants 110 120 +stressLength 0 1 2 3 4 5 6 7 +stressAdd 130 140 140 100 0 0 130 160 +stressAmp 16 16 24 24 16 16 20 24 + +tone 100 255 600 70 1200 22 2000 66 3000 12 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/robosoft4 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/robosoft4 new file mode 100644 index 0000000..5161a4f --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/robosoft4 @@ -0,0 +1,25 @@ +language variant +name Robosoft4 +echo 10 10000 + +pitch 75 115 +formant 0 100 125 100 +formant 1 96 90 80 +formant 2 97 70 90 +formant 3 97 60 90 +formant 4 97 60 90 +formant 5 100 50 90 +formant 6 90 50 100 +formant 7 100 50 100 +formant 8 100 50 100 + +roughness 5 + +intonation 10 +voicing 150 +consonants 110 120 +stressLength 0 1 2 3 4 5 6 7 +stressAdd 130 140 140 100 0 0 130 160 +stressAmp 16 16 24 24 16 16 20 24 + +tone 100 255 600 70 1200 22 2000 66 3000 12 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/robosoft5 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/robosoft5 new file mode 100644 index 0000000..2420f89 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/robosoft5 @@ -0,0 +1,25 @@ +language variant +name Robosoft5 +echo 10 10000 + +pitch 75 115 +formant 0 100 125 100 +formant 1 96 90 80 +formant 2 97 70 90 +formant 3 97 60 90 +formant 4 97 60 90 +formant 5 100 50 90 +formant 6 90 50 100 +formant 7 100 50 100 +formant 8 100 50 100 + +roughness 0 + +intonation 10 +voicing 150 +consonants 60 40 +stressLength 0 1 2 3 4 5 6 7 +stressAdd 130 140 140 100 0 0 130 160 +stressAmp 16 16 24 24 16 16 20 24 + +tone 100 255 600 70 1200 22 2000 66 3000 12 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/robosoft6 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/robosoft6 new file mode 100644 index 0000000..d3be158 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/robosoft6 @@ -0,0 +1,15 @@ +language variant +name Robosoft6 +echo 40 10000 +pitch 150 150 +formant 0 100 125 100 +formant 1 96 90 80 +formant 2 97 70 90 +formant 3 97 60 90 +formant 4 97 60 90 +formant 5 100 50 90 +formant 6 90 50 100 +formant 7 100 50 100 +formant 8 100 50 100 + +tone 100 255 600 70 1200 22 2000 66 3000 12 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/robosoft7 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/robosoft7 new file mode 100644 index 0000000..25bf759 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/robosoft7 @@ -0,0 +1,25 @@ +language variant +name Robosoft7 +echo 10 10000 + +pitch 75 115 + +formant 0 90 120 100 +formant 1 100 100 75 +formant 2 100 100 75 +formant 3 100 80 75 +formant 4 100 80 75 +formant 5 100 80 75 +formant 6 100 0 75 +formant 7 100 0 75 +formant 8 100 0 75 + +roughness 0 + +intonation 10 +voicing 150 +consonants 60 40 +stressLength 0 1 2 3 4 5 6 7 +stressAdd 130 140 140 100 0 0 130 160 + +tone 100 255 600 70 1200 22 2000 66 3000 12 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/robosoft8 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/robosoft8 new file mode 100644 index 0000000..5c84bd5 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/robosoft8 @@ -0,0 +1,16 @@ +language variant +name Robosoft8 +echo 40 10000 +pitch 150 150 +formant 0 90 120 100 +formant 1 100 100 75 +formant 2 100 100 75 +formant 3 100 80 75 +formant 4 100 80 75 +formant 5 100 80 75 +formant 6 100 0 75 +formant 7 100 0 75 +formant 8 100 0 75 + + + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/sandro b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/sandro new file mode 100644 index 0000000..6dcfe02 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/sandro @@ -0,0 +1,25 @@ +// This file is UTF-8 encoded +// Variant sandro (ver.25-3) for eSpeak-ng Copyright (C)2019 by Lolo vmanolo301@gmail.com + +language variant +name sandro +gender male + +formant 0 95 146 100 +formant 1 98 90 100 +formant 2 103 98 100 +formant 3 100 90 100 +formant 4 100 101 100 +formant 5 110 120 100 2123 +formant 6 100 100 100 1200 +formant 7 32 125 80 600 +formant 8 34 95 30 49 + +voicing 165 +consonants 194 255 +pitch 78 115 +roughness 3 +breath 20 5 2 10 5 0 27 100 +breathw 255 255 60 180 160 255 255 255 + +tone 500 210 470 70 160 155 2985 32 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/shelby b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/shelby new file mode 100644 index 0000000..21292d4 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/shelby @@ -0,0 +1,18 @@ +language variant +name shelby +flutter 0 +roughness 0 + +formant 0 100 160 190 +formant 1 90 90 90 +formant 2 140 140 140 +formant 3 130 150 130 +formant 4 110 110 110 +formant 5 120 120 110 +formant 6 10 10 10 +formant 7 10 10 10 +formant 8 10 10 10 + +pitch 100 210 +voicing 40 +consonants 90 70 \ No newline at end of file diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/steph b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/steph new file mode 100644 index 0000000..e60b0dc --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/steph @@ -0,0 +1,21 @@ +language variant +name Steph +gender female + +pitch 166 200 +flutter 1 +roughness 0 +tone 100 255 600 70 1200 22 2000 66 3000 12 + +formant 0 99 80 150 +formant 1 120 60 160 +formant 2 99 70 110 150 +formant 3 116 77 150 +formant 4 9 59 110 +formant 5 100 50 2 +formant 6 104 80 150 +formant 7 110 70 150 +formant 8 110 70 150 + +stressAmp 16 16 24 24 16 16 20 24 +consonants 55 90 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/steph2 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/steph2 new file mode 100644 index 0000000..6ac109b --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/steph2 @@ -0,0 +1,21 @@ +language variant +name Steph2 +gender female + +pitch 166 200 +flutter 1 +roughness 0 +tone 100 255 600 70 1200 22 2000 66 3000 12 + +formant 0 99 100 150 +formant 1 120 80 160 +formant 2 99 90 110 150 +formant 3 116 97 150 +formant 4 9 73 116 +formant 5 100 70 2 +formant 6 104 100 150 +formant 7 110 90 150 +formant 8 110 90 150 + +stressAmp 16 16 24 24 16 16 20 24 +consonants 55 90 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/steph3 b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/steph3 new file mode 100644 index 0000000..a925755 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/steph3 @@ -0,0 +1,22 @@ +language variant +name Steph3 +gender female + +pitch 166 200 +flutter 1 +roughness 0 +voicing 200 +tone 100 255 600 70 1200 22 2000 66 3000 12 + +formant 0 99 80 150 +formant 1 120 60 160 +formant 2 99 70 110 150 +formant 3 116 77 150 +formant 4 9 59 110 +formant 5 100 50 2 +formant 6 104 80 150 +formant 7 110 70 150 +formant 8 110 70 150 + +stressAmp 16 16 24 24 16 16 20 24 +consonants 70 90 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/travis b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/travis new file mode 100644 index 0000000..61d41a7 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/travis @@ -0,0 +1,23 @@ +language variant +name travis +gender male 30 + +pitch 75 120 + +formant 0 90 90 90 90 +formant 1 50 100 80 95 +formant 2 90 60 90 100 +formant 3 80 80 90 100 +formant 4 50 90 100 100 +formant 5 100 95 100 55 +formant 6 80 50 100 85 +formant 7 60 60 60 120 +formant 8 80 80 140 100 + +tone 600 100 1000 200 1500 50 +flutter 1 + +roughness 3 + +voicing 200 +consonants 120 190 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/victor b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/victor new file mode 100644 index 0000000..fa275e8 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/victor @@ -0,0 +1,16 @@ +language variant +name victor +gender male 25 + +formant 0 100 100 100 +formant 1 95 95 95 +formant 2 90 90 90 +formant 3 90 90 90 +formant 4 40 40 40 +formant 5 80 80 80 +formant 6 20 20 20 +formant 7 20 20 20 +formant 8 20 20 20 +pitch 80 110 +voicing 60 +breath 2 4 \ No newline at end of file diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/whisper b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/whisper new file mode 100644 index 0000000..4f8f5e8 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/whisper @@ -0,0 +1,13 @@ +language variant +name whisper +gender male + +pitch 82 117 +flutter 20 + +formant 0 100 0 100 +formant 1 100 80 100 + +voicing 17 +breath 75 75 50 40 15 10 +breathw 150 150 200 200 400 400 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/whisperf b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/whisperf new file mode 100644 index 0000000..f239e8a --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/whisperf @@ -0,0 +1,24 @@ +language variant +name female_whisper +gender female + +pitch 160 220 +roughness 3 + +formant 0 105 0 150 +formant 1 110 40 160 +formant 2 110 70 150 +formant 3 110 70 150 +formant 4 115 80 150 +formant 5 115 80 150 +formant 6 110 70 150 +formant 7 110 70 150 +formant 8 110 70 150 + +stressAdd 0 0 -10 -10 0 0 10 40 + +// whisper +voicing 20 +breath 75 75 50 40 15 10 +breathw 150 150 200 200 400 400 + diff --git a/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/zac b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/zac new file mode 100644 index 0000000..aa17d42 --- /dev/null +++ b/system/tts_engines/piper/engine/espeak-ng-data/voices/!v/zac @@ -0,0 +1,15 @@ +language variant +name Zac +flutter 5 + +pitch 240 390 +formant 0 145 100 145 +formant 1 145 100 145 +formant 2 145 100 145 +formant 3 145 100 145 +formant 4 145 100 145 +formant 5 145 120 145 +formant 6 145 120 145 +formant 7 145 120 145 +formant 8 145 120 145 +voicing 80 diff --git a/system/tts_engines/piper/engine/espeak-ng-data/yue_dict b/system/tts_engines/piper/engine/espeak-ng-data/yue_dict new file mode 100644 index 0000000..ed9cf07 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng-data/yue_dict differ diff --git a/system/tts_engines/piper/engine/espeak-ng.dll b/system/tts_engines/piper/engine/espeak-ng.dll new file mode 100644 index 0000000..5dcb689 Binary files /dev/null and b/system/tts_engines/piper/engine/espeak-ng.dll differ diff --git a/system/tts_engines/piper/engine/libtashkeel_model.ort b/system/tts_engines/piper/engine/libtashkeel_model.ort new file mode 100644 index 0000000..7bd74eb Binary files /dev/null and b/system/tts_engines/piper/engine/libtashkeel_model.ort differ diff --git a/system/tts_engines/piper/engine/onnxruntime.dll b/system/tts_engines/piper/engine/onnxruntime.dll new file mode 100644 index 0000000..2807f68 Binary files /dev/null and b/system/tts_engines/piper/engine/onnxruntime.dll differ diff --git a/system/tts_engines/piper/engine/onnxruntime_providers_shared.dll b/system/tts_engines/piper/engine/onnxruntime_providers_shared.dll new file mode 100644 index 0000000..f82687b Binary files /dev/null and b/system/tts_engines/piper/engine/onnxruntime_providers_shared.dll differ diff --git a/system/tts_engines/piper/engine/piper.exe b/system/tts_engines/piper/engine/piper.exe new file mode 100644 index 0000000..47d908d Binary files /dev/null and b/system/tts_engines/piper/engine/piper.exe differ diff --git a/system/tts_engines/piper/engine/piper_phonemize.dll b/system/tts_engines/piper/engine/piper_phonemize.dll new file mode 100644 index 0000000..eceaf31 Binary files /dev/null and b/system/tts_engines/piper/engine/piper_phonemize.dll differ diff --git a/system/tts_engines/piper/engine/test.json b/system/tts_engines/piper/engine/test.json new file mode 100644 index 0000000..408dbc5 --- /dev/null +++ b/system/tts_engines/piper/engine/test.json @@ -0,0 +1 @@ +{ "text": "First sentence to speak." } \ No newline at end of file diff --git a/system/tts_engines/piper/model_engine.py b/system/tts_engines/piper/model_engine.py new file mode 100644 index 0000000..438e7be --- /dev/null +++ b/system/tts_engines/piper/model_engine.py @@ -0,0 +1,472 @@ +############################################### +# DONT CHANGE # These are base imports needed # +############################################### +import os +import sys +import json +import time +import torch +import logging +from pathlib import Path +from fastapi import (HTTPException) +logging.disable(logging.WARNING) +################################################################# +# DONT CHANGE # Get Pytorch & Python versions & setup DeepSpeed # +################################################################# +pytorch_version = torch.__version__ +cuda_version = torch.version.cuda +major, minor, micro = sys.version_info[:3] +python_version = f"{major}.{minor}.{micro}" +try: + import deepspeed + deepspeed_available = True +except ImportError: + deepspeed_available = False + pass + +############################################################################################################# +############################################################################################################# +# CHANGE ME # Run any specifc imports, requirements or setup any global vaiables needed for this TTS Engine # +############################################################################################################# +############################################################################################################# +# In this section you will import any imports that your specific TTS Engine will use. You will provide any +# start-up errors for those bits, as if you were starting up a normal Python script. Note the logging.disable +# a few lines up from here, you may want to # that out while debugging! +import asyncio + + +################################################################################################################################# +# DONT CHANGE # Do not change the Class name from tts_class as this is what will be imported into the main tts_server.py script # +################################################################################################################################# +class tts_class: + def __init__(self): + ######################################################################## + # DONT CHANGE # Sets up the base variables required for any tts engine # + ######################################################################## + self.branding = None + self.this_dir = Path(__file__).parent.resolve() # Sets up self.this_dir as a variable for the folder THIS script is running in. + self.main_dir = Path(__file__).parent.parent.parent.parent.resolve() # Sets up self.main_dir as a variable for the folder AllTalk is running in + self.device = "cuda" if torch.cuda.is_available() else "cpu" # Sets up self.device to cuda if torch exists with Nvidia/CUDA, otherwise sets to cpu + self.cuda_is_available = torch.cuda.is_available() # Sets up cuda_is_available as a True/False to track if Nvidia/CUDA was found on the system + self.tts_generating_lock = False # Used to lock and unlock the tts generation process at the start/end of tts generation. + self.tts_stop_generation = False # Used in conjunction with tts_generating_lock to call for a stop to the current generation. If called (set True) it needs to be set back to False when generation has been stopped. + self.tts_narrator_generatingtts = False # Used to track if the current tts processes is narrator based. This can be used in conjunction with lowvram and device to avoid moving model between GPU(CUDA)<>RAM(CPU) each chunk of narrated text generated. + self.model = None # If loading a model into CUDA/VRAM/RAM "model" is used as the variable name to load and interact with (see the XTTS model_engine script for examples.) + self.is_tts_model_loaded = False # Used to track if a model is actually loaded in and error/fail things like TTS generation if its False + self.current_model_loaded = None # Stores the name of the currenly loaded in model + self.available_models = None # List of available models found by "def scan_models_folder" + self.setup_has_run = False # Tracks if async def setup(self) has run, by setting to True, so that the /api/ready endpoint can provide a "Ready" status + ############################################################################################## + # DONT CHANGE # Load in a list of the available TTS engines and the currently set TTS engine # + ############################################################################################## + tts_engines_file = os.path.join(self.main_dir, "system", "tts_engines", "tts_engines.json") + with open(tts_engines_file, "r") as f: + tts_engines_data = json.load(f) + self.engines_available = [engine["name"] for engine in tts_engines_data["engines_available"]] # A list of ALL the TTS engines available to be loaded by AllTalk + self.engine_loaded = tts_engines_data["engine_loaded"] # In "tts_engines.json" what is the currently set TTS engine loading into AllTalk + self.selected_model = tts_engines_data["selected_model"] # In "tts_engines.json" what is the currently set TTS model loading into AllTalk + ############################################################################ + # DONT CHANGE # Pull out all the settings for the currently set TTS engine # + ############################################################################ + with open(os.path.join(self.this_dir, "model_settings.json"), "r") as f: + tts_model_loaded = json.load(f) + # Access the model details + self.manufacturer_name = tts_model_loaded["model_details"]["manufacturer_name"] # The company/person/body that generated the TTS engine/models etc + self.manufacturer_website = tts_model_loaded["model_details"]["manufacturer_website"] # The website of the company/person/body where people can find more information + # Access the features the model is capable of: + self.audio_format = tts_model_loaded["model_capabilties"]["audio_format"] # This details the audio format your TTS engine is set to generate TTS in e.g. wav, mp3, flac, opus, acc, pcm. Please use only 1x format. + self.deepspeed_capable = tts_model_loaded["model_capabilties"]["deepspeed_capable"] # Is your model capable of DeepSpeed + self.deepspeed_available = 'deepspeed' in globals() # When we did the import earlier, at the top of this script, was DeepSpeed available for use + self.generationspeed_capable = tts_model_loaded["model_capabilties"]["generationspeed_capable"] # Does this TTS engine support changing the speed of the generated TTS + self.languages_capable = tts_model_loaded["model_capabilties"]["languages_capable"] # Are the actual models themselves capable of generating in multiple languages OR is each model language specific + self.lowvram_capable = tts_model_loaded["model_capabilties"]["lowvram_capable"] # Is this engine capable of using low VRAM (moving the model between CPU And GPU memory) + self.multimodel_capable = tts_model_loaded["model_capabilties"]["multimodel_capable"] # Is there just the one model or are there multiple models this engine supports. + self.repetitionpenalty_capable = tts_model_loaded["model_capabilties"]["repetitionpenalty_capable"] # Is this TTS engine capable of changing the repititon penalty + self.streaming_capable = tts_model_loaded["model_capabilties"]["streaming_capable"] # Is this TTS engine capabale of generating streaming audio + self.temperature_capable = tts_model_loaded["model_capabilties"]["temperature_capable"] # Is this TTS engine capable of changing the temperature of the models + self.multivoice_capable = tts_model_loaded["model_capabilties"]["multivoice_capable"] # Are the models multi-voice or single vocice models + self.pitch_capable = tts_model_loaded["model_capabilties"]["pitch_capable"] # Is this TTS engine capable of changing the pitch of the genrated TTS + # Access the current enginesettings + self.def_character_voice = tts_model_loaded["settings"]["def_character_voice"] # What is the current default main/character voice that will be used if no voice specified. + self.def_narrator_voice = tts_model_loaded["settings"]["def_narrator_voice"] # What is the current default narrator voice that will be used if no voice specified. + self.deepspeed_enabled = tts_model_loaded["settings"]["deepspeed_enabled"] # If its available, is DeepSpeed enabled for the TTS engine + self.engine_installed = tts_model_loaded["settings"]["engine_installed"] # Has the TTS engine been setup/installed (not curently used) + self.generationspeed_set = tts_model_loaded["settings"]["generationspeed_set"] # What is the set/stored speed for generation. + self.lowvram_enabled = tts_model_loaded["settings"]["lowvram_enabled"] # If its available, is LowVRAM enabled for the TTS engine + # Check if someone has enabled lowvram on a system that's not CUDA enabled + self.lowvram_enabled = False if not torch.cuda.is_available() else self.lowvram_enabled # If LowVRAM is mistakenly set and CUDA is not available, this will force it back off + self.repetitionpenalty_set = tts_model_loaded["settings"]["repetitionpenalty_set"] # What is the currenly set repitition policy of the model (If it support repetition) + self.temperature_set = tts_model_loaded["settings"]["temperature_set"] # What is the currenly set temperature of the model (If it support temp) + self.pitch_set = tts_model_loaded["settings"]["pitch_set"] # What is the currenly set pitch of the model (If it support temp) + # Gather the OpenAI API Voice Mappings + self.openai_alloy = tts_model_loaded["openai_voices"]["alloy"] # The TTS engine voice that will be mapped to Open AI Alloy voice + self.openai_echo = tts_model_loaded["openai_voices"]["echo"] # The TTS engine voice that will be mapped to Open AI Echo voice + self.openai_fable = tts_model_loaded["openai_voices"]["fable"] # The TTS engine voice that will be mapped to Open AI Fable voice + self.openai_nova = tts_model_loaded["openai_voices"]["nova"] # The TTS engine voice that will be mapped to Open AI Nova voice + self.openai_onyx = tts_model_loaded["openai_voices"]["onyx"] # The TTS engine voice that will be mapped to Open AI Onyx voice + self.openai_shimmer = tts_model_loaded["openai_voices"]["shimmer"] # The TTS engine voice that will be mapped to Open AI Shimmer voice + ################################################################### + # DONT CHANGE # Load params and api_defaults from confignew.json # + ################################################################### + # Define the path to the confignew.json file + configfile_path = self.main_dir / "confignew.json" + # Load config file and get settings + with open(configfile_path, "r") as configfile: + configfile_data = json.load(configfile) + self.branding = configfile_data.get("branding", "") # Sets up self.branding for outputting the name stored in the "confgnew.json" file, as used in print statements. + self.params = configfile_data # Loads in the curent "confgnew.json" file to self.params. + self.debug_tts = configfile_data.get("debugging").get("debug_tts") # Can be used within this script as a True/False flag for generally debugging the TTS generation process. + self.debug_tts_variables = configfile_data.get("debugging").get("debug_tts_variables") # Can be used within this script as a True/False flag for generally debugging variables (if you wish). + ################################################################ + # DONT CHANGE # Print out Python, CUDA, DeepSpeed versions #### + ################################################################ + def printout_versions(self): + if deepspeed_available: + print(f"[{self.branding}ENG] \033[92mDeepSpeed version :\033[93m",deepspeed.__version__,"\033[0m") + else: + print(f"[{self.branding}ENG] \033[92mDeepSpeed version :\033[93m Not available\033[0m") + print(f"[{self.branding}ENG] \033[92mPython Version :\033[93m {python_version}\033[0m") + print(f"[{self.branding}ENG] \033[92mPyTorch Version :\033[93m {pytorch_version}\033[0m") + if cuda_version is None: + print(f"[{self.branding}ENG] \033[92mCUDA Version :\033[91m Not available\033[0m") + else: + print(f"[{self.branding}ENG] \033[92mCUDA Version :\033[93m {cuda_version}\033[0m") + print(f"[{self.branding}ENG]") + return + + ################################################################################### + ################################################################################### + # CHANGE ME # Inital setup of the model and engine. Called when the script starts # + ################################################################################### + ################################################################################### + # In here you will add code to load in your model and do its inital setup. So you + # may be calling your model loader via handle_tts_method_change or if your TTS + # engine doesnt actually load a model into CUDA or System RAM, you may be doing + # Something to fake its start-up. + async def setup(self): + self.printout_versions() + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + # Set a timer to calculate load times + generate_start_time = time.time() # Record the start time of loading the model - Piper doesnt load a model, so its a bit unique that we put a load time in here. + self.available_models = self.scan_models_folder() + print("in setup - self.selected_model:", self.selected_model) if self.debug_tts else None + self.current_model_loaded = "piper" + self.is_tts_model_loaded = True # Set self.is_tts_model_loaded to True so that other portions of AllTalk know that the script is ready to recieve generation requests, as piper doesnt load in models + generate_end_time = time.time() # Create an end timer for calculating load times + generate_elapsed_time = generate_end_time - generate_start_time # Calculate start time minus end time + # Print out the result of the load time + print(f"[{self.branding}ENG]\033[94m Model/Engine :\033[93m Piper\033[94m Ready\033[0m") + print(f"[{self.branding}ENG] \033[94mLoad time :\033[93m {generate_elapsed_time:.2f} seconds.\033[0m") + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + self.setup_has_run = True # Flag that setup has run, so the /api/ready endpoint will send a "Ready" status and load the webui + + ################################## + ################################## + # CHANGE ME # Low VRAM Swapping # + ################################## + ################################## + # If your model does load into CUDA and you want to support LowVRAM, aka moving the model + # on the fly between CUDA and System RAM on each generation request, you will be adding + # The code in here to do it. See the XTTS tts engine for an example. Piper however, doesnt + # Load into VRAM or System RAM, also low vram is set globally disabled by the model_settings.JSON + # file, so this would never get called anywyay, however, we still need to keep an empty + # function in place. The "pass" tells the function to just exit out cleanly if called. + # However, its quite a simple check along the lines of "if CUDA is available and model is + # in X place, then send it to Y place (or Y to X). + async def handle_lowvram_change(self): + pass # Piper does not stay in CUDA or support swapping of location + + ######################################## + ######################################## + # CHANGE ME # DeepSpeed model loading # + ######################################## + ######################################## + # If the model supports CUDA and DeepSpeed, this is where you can handle re-loading + # the model as/when DeepSpeed is enabled/selected in the user interface. If your + # TTS model doesnt support DeepSpeed, then it should be globally set in your + # model_settings.JSON and this function will never get called, however it still needs + # to exist as a function. + async def handle_deepspeed_change(self, value): + if value: + # DeepSpeed enabled + print(f"[{self.branding}ENG] \033[93mDeepSpeed Activating\033[0m") + await self.unload_model() + self.params["tts_method_api_local"] = False + self.params["tts_method_xtts_local"] = True + self.deepspeed_enabled = True + await self.setup() + else: + # DeepSpeed disabled + print(f"[{self.branding}ENG] \033[93mDeepSpeed De-Activating\033[0m") + self.deepspeed_enabled = False + await self.unload_model() + await self.setup() + return value # Return new checkbox value + + ############################################################################################################################################## + ############################################################################################################################################## + # CHANGE ME # scan for available models/voices that are relevant to this TTS engine # XTTS is very unusal as it has 2x model loading methods # + ############################################################################################################################################## + ############################################################################################################################################## + # This function looks and reports back the list of possible models your TTS engine can + # load in. Some TTS engines have multiple models they can load and you will want to use + # code for checking if the models are in the correct location/placement within the disk + # the correct files exst per model etc (check XTTS for an example of this). Some models + # like Piper, the actual models are the voices, so in the Piper scan_models_folder + # function, we fake the only model being available as {'piper': 'piper'} aka, model name + # then engine name, then we use the voices_file_list to populate the models as available + # voices that can be selected in the interface. + # If no models are found, we return "No Models Available" and continue on with the script. + def scan_models_folder(self): + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + self.available_models = {'piper': 'piper'} + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + if not self.available_models: + self.available_models["No Models Found"] = "No Models Found" # Return a list with {'No Models Found'} if there are no models found. + return self.available_models # Return a list of models in the format {'engine name': 'model 1', 'engine name': 'model 2", etc} + + ############################################################# + ############################################################# + # CHANGE ME # POPULATE FILES LIST FROM VOICES DIRECTORY #### + ############################################################# + ############################################################# + # This function looks and reports back the list of possible voice your TTS engine can + # load in. Some TTS engines the voices are wav file samples (XTTS), some are models + # (Piper) and some are text (Parler) thats stored in a JSON file. We just need to + # populate the "voices" variable somehow and if no voices are found, we return + # "No Voices Found" back to the interface/api. + def voices_file_list(self): + try: + voices = [] + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + models_dir = self.main_dir / "models" / "piper" + for root, dirs, files in os.walk(models_dir): + onnx_files = [file for file in files if file.endswith(".onnx")] + for onnx_file in onnx_files: + json_file = onnx_file + ".json" + if json_file in files: + relative_path = os.path.relpath(os.path.join(root, onnx_file), models_dir) + voices.append(relative_path) + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + if not voices: + return ["No Voices Found"] # Return a list with {'No Voices Found'} if there are no voices/voice models. + return voices # Return a list of models in the format {'engine name': 'model 1', 'engine name': 'model 2", etc} + except Exception as e: + print(f"[{self.branding}ENG] \033[91mError\033[0m: Voices/Voice Models not found. Cannot load a list of voices.") + print(f"[{self.branding}ENG]") + return ["No Voices Found"] + + ################################################################################# + ################################################################################# + # CHANGE ME # Model loading # Piper does not actually load/stay resident in RAM # + ################################################################################# + ################################################################################# + # This function will handle the loading of your model, into VRAM/CUDA, System RAM or whatever. + # In XTTS, which has 2x model loader types, there are 2x loaders. They are called by "def handle_tts_method_change" + # In Piper we fake a model loader as Piper doesnt actually load a model into CUDA/System RAM as such. So, in that + # situation, api_manual_load_model is kind of a blank function. Though we do set self.is_tts_model_loaded = True + # as this is used elsewhere in the scripts to confirm that a model is available to be used for TTS generation. + # We always check for "No Models Available" being sent as that means we are trying to load in a model that + # doesnt exist/wasnt found on script start-up e.g. someone deleted the model from the folder or something. + async def api_manual_load_model(self, model_name): + if model_name == "No Models Found": + print(f"[{self.branding}ENG] \033[91mError\033[0m: No models for this TTS engine were found to load.") + raise HTTPException(status_code=400, detail="No models for this TTS engine were found to load.") + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + self.is_tts_model_loaded = True + return None + + ############################### + ############################### + # CHANGE ME # Model unloading # + ############################### + ############################### + # This function will handle the UN-loading of your model, from VRAM/CUDA, System RAM or whatever. + # In XTTS, that model loads into CUDA/System Ram, so when we swap models, we want to unload the current model + # free up the memory and then load in the new model to VRAM/CUDA. On the flip side of that, Piper doesnt + # doesnt load into memory, so we just need to put a fake function here that doesnt really do anything + # other than set "self.is_tts_model_loaded = False", which would be set back to true by the model loader. + # So look at the Piper model_engine.py if you DONT need to unload models. + async def unload_model(self): + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + self.is_tts_model_loaded = False + return None + + ################################################################################################################################### + ################################################################################################################################### + # CHANGE ME # Model changing. Unload out old model and load in a new one # XTTS is very unusal as it has 2x model loading methods # + ################################################################################################################################### + ################################################################################################################################### + # This function is your central model loading/unloading handler that deals with the above functions as necesary, to call loading, unloading, + # swappng DeepSpeed, Low vram etc. This function gets called with a "engine name - model name" type call. In XTTS, because there are 2x + # model loader types, (XTTS and APILocal), we take tts_method and split the "engine name - model name" into a loader type and the model + # that it needs to load in and then we call the correct loader function. Whereas in Piper, which doesnt load models into memory at all, + # we just have a fake function that doesnt really do anything. We always check to see if the model name has "No Models Available" in the + # name thats sent over, just to catch any potential errors. We display the start load time and end load time. Thats about it. + async def handle_tts_method_change(self, tts_method): + generate_start_time = time.time() # Record the start time of loading the model + if "No Models Available" in self.available_models: + print(f"[{self.branding}ENG] \033[91mError\033[0m: No models for this TTS engine were found to load. Please download a model.") + return False + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + generate_end_time = time.time() # Create an end timer for calculating load times + generate_elapsed_time = generate_end_time - generate_start_time # Calculate start time minus end time + print(f"[{self.branding}ENG] \033[94mLoad time :\033[93m {generate_elapsed_time:.2f} seconds.\033[0m") # Print out the result of the load time + return True + + ########################################################################################################################################## + ########################################################################################################################################## + # CHANGE ME # Model changing. Unload out old model and load in a new one # XTTS is very unusal as it has 2x model TTS generation methods # + ########################################################################################################################################## + ########################################################################################################################################## + # In here all the possible options are sent over (text, voice to use, lanugage, speed etc etc) and its up to you how you use them, or not. + # obviously if your TTS engine doesnt support speed for example, generationspeed_capable should be set False in your model_settings.JSON file + # and a fake "generationspeed_set" value should be set. This allows a fake value to be sent over from the main script, even though it + # wouldnt actually ever be used in the generation below. Nonethless all these values, real or just whats inside the configuration file + # will be sent over for use. + # Setting the xxxxxxx_capabale in the model_settings.JSON file, will enable/disable them being selectable by the user. For example, if you + # set "generationspeed_capable" as false in the model_settings.JSON file, a user will not be able to select OR set the setting for + # generation speed. + # One thing to note is that we HAVE to have something in this generation request that is synchronous from the way its called, which means + # we have to have an option for Streaming, even if our TTS engine doesnt support streaming. So in that case, we would set streaming_capable + # as false in our model_settings.JSON file, meaning streaming will never be called. However, we have to put a fake streaming routine in our + # function below (or a real function if it does support streaming of course). Parler has an example of a fake streaming function, which is + # very clearly highlighted in its model_engine.py script. + # Piper TTS, which uses command line based calls and therefore has different ones for Windows and Linux/Mac, has an example of doing this + # within its model_engine.py file. + async def generate_tts(self, text, voice, language, temperature, repetition_penalty, speed, pitch, output_file, streaming): + if voice == "No Voices Found": + print(f"[{self.branding}ENG] \033[91mError\033[0m: No voices found to generate TTS.") + raise HTTPException(status_code=400, detail="No voices found to generate TTS.") + if not self.is_tts_model_loaded: + print(f"[{self.branding}ENG] \033[91mError\033[0m: You currently have no TTS model loaded.") + raise HTTPException(status_code=400, detail="You currently have no TTS model loaded.") + self.tts_generating_lock = True # Lock the process to say its currently in use + generate_start_time = time.time() + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + model_dir = self.main_dir / "models" / "piper" / voice + model_file = model_dir.with_suffix(".onnx") + config_file = model_file.with_suffix(".onnx.json") + inverted_speed = 1.0 / float(speed) + + if sys.platform == "win32": + # Windows: Use piper.exe + piper_exe = self.main_dir / "system" / "tts_engines" / "piper" / "engine" / "piper.exe" + # Ensure all files exist + if not piper_exe.exists(): + print(f"[{self.branding}Debug] Piper executable not found: {piper_exe}") if self.debug_tts else None + raise HTTPException(status_code=500, detail="Piper executable not found.") + if not model_file.exists(): + print(f"[{self.branding}Debug] Model file not found: {model_file}") if self.debug_tts else None + raise HTTPException(status_code=500, detail="Model file not found.") + if not config_file.exists(): + print(f"[{self.branding}Debug] Config file not found: {config_file}") if self.debug_tts else None + raise HTTPException(status_code=500, detail="Config file not found.") + command = [ + str(piper_exe), + "--model", str(model_file), + "--config", str(config_file), + "--output_file", str(output_file), + "--length_scale", str(inverted_speed), + ] + if streaming: + print(f"[{self.branding}Debug] Streaming enabled") if self.debug_tts else None + command.append("--output-raw") + else: + # Linux and macOS: Use piper command directly # In piper there are different call methods between Windows and Linux/Mac + if not model_file.exists(): + print(f"[{self.branding}Debug] Model file not found: {model_file}") if self.debug_tts else None + raise HTTPException(status_code=500, detail="Model file not found.") + if not config_file.exists(): + print(f"[{self.branding}Debug] Config file not found: {config_file}") if self.debug_tts else None + raise HTTPException(status_code=500, detail="Config file not found.") + command = [ + "piper", + "-m", str(model_file), + "-c", str(config_file), + "-f", str(output_file), + "--length-scale", str(inverted_speed), + ] + if streaming: + print(f"[{self.branding}Debug] Streaming enabled") if self.debug_tts else None + command.append("--output-raw") + + try: + print(f"[{self.branding}Debug] Creating subprocess") if self.debug_tts else None + process = await asyncio.create_subprocess_exec( + *command, + stdin=asyncio.subprocess.PIPE, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + print(f"[{self.branding}Debug] Communicating with subprocess") if self.debug_tts else None + stdout, stderr = await process.communicate(text.encode()) + if stderr: + print(f"[{self.branding}Debug] Subprocess stderr: {stderr.decode()}") if self.debug_tts else None + return_code = process.returncode + print(f"[{self.branding}Debug] Subprocess return code: {return_code}") if self.debug_tts else None + if return_code != 0: + print(f"[{self.branding}Debug] Subprocess failed with return code: {return_code}") if self.debug_tts else None + raise HTTPException(status_code=500, detail=f"Subprocess failed with return code: {return_code}") + if not os.path.exists(output_file): + print(f"[{self.branding}Debug] Output file does not exist: {output_file}") if self.debug_tts else None + if streaming: + print(f"[{self.branding}Debug] Yielding stdout") if self.debug_tts else None + yield stdout + else: + print(f"[{self.branding}Debug] Yielding empty byte string") if self.debug_tts else None + yield b"" + + except Exception as e: + print(f"[{self.branding}Debug] Exception while running subprocess: {str(e)}") if self.debug_tts else None + raise HTTPException(status_code=500, detail=f"Exception while running subprocess: {str(e)}") + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + generate_end_time = time.time() + generate_elapsed_time = generate_end_time - generate_start_time + print(f"[{self.branding}GEN] \033[94mTTS Generate: \033[93m{generate_elapsed_time:.2f} seconds. \033[94mLowVRAM: \033[33m{self.lowvram_enabled} \033[94mDeepSpeed: \033[33m{self.deepspeed_enabled}\033[0m") + self.tts_generating_lock = False diff --git a/system/tts_engines/piper/model_settings.json b/system/tts_engines/piper/model_settings.json new file mode 100644 index 0000000..d934545 --- /dev/null +++ b/system/tts_engines/piper/model_settings.json @@ -0,0 +1,42 @@ +{ + "model_details": { + "manufacturer_name": "Piper", + "manufacturer_website": "https://github.com/rhasspy/piper", + "model_description": "A fast, lightweight (low resource requirement), local neural text to speech system that sounds great and is optimized for the Raspberry Pi 4. The MODEL_CARD file for each voice contains important licensing information. Piper is intended for text to speech research, and does not impose any additional restrictions on voice models. Some voices may have restrictive licenses, however, so please review them carefully!" + }, + "model_capabilties": { + "audio_format": "wav", + "deepspeed_capable": false, + "generationspeed_capable": true, + "languages_capable": false, + "lowvram_capable": false, + "multimodel_capable": true, + "repetitionpenalty_capable": false, + "streaming_capable": false, + "temperature_capable": false, + "multivoice_capable": true, + "pitch_capable": false, + "windows_capable": true, + "linux_capable": true, + "mac_capable": true + }, + "settings": { + "def_character_voice": "en_US-ljspeech-high.onnx", + "def_narrator_voice": "en_US-ljspeech-high.onnx", + "deepspeed_enabled": false, + "engine_installed": true, + "generationspeed_set": 1, + "lowvram_enabled": false, + "pitch_set": 0, + "repetitionpenalty_set": 10, + "temperature_set": 0.75 + }, + "openai_voices": { + "alloy": "en_US-ljspeech-high.onnx", + "echo": "en_US-ljspeech-high.onnx", + "fable": "en_US-ljspeech-high.onnx", + "nova": "en_US-ljspeech-high.onnx", + "onyx": "en_US-ljspeech-high.onnx", + "shimmer": "en_US-ljspeech-high.onnx" + } +} diff --git a/system/tts_engines/piper/piper_gitclone_scanner.py b/system/tts_engines/piper/piper_gitclone_scanner.py new file mode 100644 index 0000000..85d1f29 --- /dev/null +++ b/system/tts_engines/piper/piper_gitclone_scanner.py @@ -0,0 +1,53 @@ +import os +import json +from urllib.parse import urljoin + +REPO_PATH = "E:/alltalk_tts/models/piper/" +DISK_PATH_PREFIX = "/piper" +print(f"Current Working Directory: {os.getcwd()}") +print(f"Contents of {REPO_PATH}:") +for root, dirs, files in os.walk(REPO_PATH): + print(f"Directory: {root}") + for file in files: + print(f" File: {os.path.join(root, file)}") + +HF_REPO_URL = "https://huggingface.co/rhasspy/piper-voices/resolve/main/" + +def get_file_info(onnx_path, repo_url): + base_name = os.path.splitext(os.path.basename(onnx_path))[0] + rel_onnx_path = os.path.relpath(onnx_path, REPO_PATH).replace("\\", "/") + download_url_onnx = urljoin(repo_url, rel_onnx_path) + "?download=true" + json_path = None + for root, dirs, files in os.walk(os.path.dirname(onnx_path)): + for file in files: + if file.endswith(".json") and base_name in file: + json_path = os.path.join(root, file) + rel_json_path = os.path.relpath(json_path, REPO_PATH).replace("\\", "/") + download_url_json = urljoin(repo_url, rel_json_path) + "?download=true" + break + if json_path: + return { + "name": base_name, + "files_download": [download_url_onnx, download_url_json], + } + else: + return None + +voice_files = [] +for root, dirs, files in os.walk(REPO_PATH): + for file in files: + if file.endswith(".onnx"): + onnx_path = os.path.join(root, file) + print(f"Found ONNX file: {onnx_path}") + file_info = get_file_info(onnx_path, HF_REPO_URL) + if file_info: + voice_files.append(file_info) + else: + print(f"No associated JSON file found for {onnx_path}") + +if not voice_files: + print("No ONNX/JSON file pairs found.") +else: + print(f"Found {len(voice_files)} ONNX/JSON file pairs.") + with open("voice_files.json", "w") as f: + json.dump(voice_files, f, indent=2) \ No newline at end of file diff --git a/system/tts_engines/piper/piper_settings_page.py b/system/tts_engines/piper/piper_settings_page.py new file mode 100644 index 0000000..0a53769 --- /dev/null +++ b/system/tts_engines/piper/piper_settings_page.py @@ -0,0 +1,320 @@ +import os +import json +import requests +import gradio as gr +from tqdm import tqdm +from pathlib import Path +this_dir = Path(__file__).parent.resolve() # Sets up self.this_dir as a variable for the folder THIS script is running in. +main_dir = Path(__file__).parent.parent.parent.parent.resolve() # Sets up self.main_dir as a variable for the folder AllTalk is running in + +########################################################################## +# REQUIRED CHANGE # +# Populate the voices list, using the method specific to your TTS engine # +########################################################################## +# This function is responsible for populating the list of available voices for your TTS engine. +# You need to modify this function to use the appropriate method for your engine to retrieve the voice list. +# +# The current implementation lists all the WAV files in a "voices" directory, which may not be suitable for your engine. +# You should replace the `xxxx_voices_file_list` function name to match your engine name. For example, if your engine +# is named "mytts", the function should be named `mytts_voices_file_list`. +# +# You will also neef to update the code with your own implementation that retrieves the voice list according to your +# engine's specific requirements. Typically this is the same code as will be in your model_engine.py file. +# +# For example, if your engine has a dedicated API or configuration file for managing voices, you should modify this +# function to interact with that API or read from that configuration file. +# +# After making the necessary changes, this function should return a list of available voices that can be used +# in your TTS engine's settings page. +def piper_voices_file_list(): + voices = [] + models_dir = main_dir / "models" / "piper" + + for root, dirs, files in os.walk(models_dir): + onnx_files = [file for file in files if file.endswith(".onnx")] + for onnx_file in onnx_files: + json_file = onnx_file + ".json" + if json_file in files: + relative_path = os.path.relpath(os.path.join(root, onnx_file), models_dir) + voices.append(relative_path) + + return voices + +###################################################### +# REQUIRED CHANGE # +# Imports and saves the TTS engine-specific settings # +###################################################### +# This function is responsible for importing and saving the settings specific to your TTS engine. +# You need to make the following change: +# +# 1. Change the name of the function `xxxx_model_update_settings` to match your engine's name. +# For example, if your engine is named "mytts", the function should be named `mytts_model_update_settings`. +# +# After making this change, the function will load the model settings from a JSON file, update the settings and voice +# dictionaries with the values provided as arguments, and save the updated settings back to the JSON file. +# +# You do not need to modify the function's logic or any other part of the code. +def piper_model_update_settings(def_character_voice_gr, def_narrator_voice_gr, lowvram_enabled_gr, deepspeed_enabled_gr, temperature_set_gr, repetitionpenalty_set_gr, pitch_set_gr, generationspeed_set_gr, alloy_gr, echo_gr, fable_gr, nova_gr, onyx_gr, shimmer_gr): + # Load the model_config_data from the JSON file + with open(os.path.join(this_dir, "model_settings.json"), "r") as f: + model_config_data = json.load(f) + # Update the settings and openai_voices dictionaries with the new values + model_config_data["settings"]["def_character_voice"] = def_character_voice_gr + model_config_data["settings"]["def_narrator_voice"] = def_narrator_voice_gr + model_config_data["openai_voices"]["alloy"] = alloy_gr + model_config_data["openai_voices"]["echo"] = echo_gr + model_config_data["openai_voices"]["fable"] = fable_gr + model_config_data["openai_voices"]["nova"] = nova_gr + model_config_data["openai_voices"]["onyx"] = onyx_gr + model_config_data["openai_voices"]["shimmer"] = shimmer_gr + model_config_data["settings"]["lowvram_enabled"] = lowvram_enabled_gr == "Enabled" + model_config_data["settings"]["deepspeed_enabled"] = deepspeed_enabled_gr == "Enabled" + model_config_data["settings"]["temperature_set"] = temperature_set_gr + model_config_data["settings"]["repetitionpenalty_set"] = repetitionpenalty_set_gr + model_config_data["settings"]["pitch_set"] = pitch_set_gr + model_config_data["settings"]["generationspeed_set"] = generationspeed_set_gr + # Save the updated model_config_data to the JSON file + with open(os.path.join(this_dir, "model_settings.json"), "w") as f: + json.dump(model_config_data, f, indent=4) + return "Settings updated successfully!" + +####################################################### +# REQUIRED CHANGE # +# Sets up the engine-specific settings page in Gradio # +####################################################### +# This function sets up the Gradio interface for the settings page specific to your TTS engine. +# You need to make the following changes: +# +# 1. Change the name of the function `xxxx_model_alltalk_settings` to match your engine's name. +# For example, if your engine is named "mytts", the function should be named `mytts_model_alltalk_settings`. +# +# 2. Change the name of the `submit_button.click` function call to match the name you gave to the function +# that imports and saves your engine's settings (the function you modified above). +# +# 3. Change the name of the `voice_list` function call to match the name of the function that lists +# the available voices for your TTS engine. +# +# 4. Change the 'title' of the `gr.Blocks` to match your engine's name e.g. title="mytts TTS" +# +# After making these changes, this function will create and return the Gradio interface for your TTS engine's +# settings page, allowing users to configure various options and voice selections. +def piper_model_alltalk_settings(model_config_data): + features_list = model_config_data['model_capabilties'] + voice_list = piper_voices_file_list() + with gr.Blocks(title="piper TTS") as app: + with gr.Tab("Default Settings"): + with gr.Row(): + lowvram_enabled_gr = gr.Radio(choices={"Enabled": "true", "Disabled": "false"}, label="Low VRAM" if model_config_data["model_capabilties"]["lowvram_capable"] else "Low VRAM N/A", value="Enabled" if model_config_data["settings"]["lowvram_enabled"] else "Disabled", interactive=model_config_data["model_capabilties"]["lowvram_capable"]) + deepspeed_enabled_gr = gr.Radio(choices={"Enabled": "true", "Disabled": "false"}, label="DeepSpeed Activate" if model_config_data["model_capabilties"]["deepspeed_capable"] else "DeepSpeed N/A", value="Enabled" if model_config_data["settings"]["deepspeed_enabled"] else "Disabled", interactive=model_config_data["model_capabilties"]["deepspeed_capable"]) + temperature_set_gr = gr.Slider(value=float(model_config_data["settings"]["temperature_set"]), minimum=0, maximum=1, step=0.05, label="Temperature" if model_config_data["model_capabilties"]["temperature_capable"] else "Temperature N/A", interactive=model_config_data["model_capabilties"]["temperature_capable"]) + repetitionpenalty_set_gr = gr.Slider(value=float(model_config_data["settings"]["repetitionpenalty_set"]), minimum=1, maximum=20, step=1, label="Repetition Penalty" if model_config_data["model_capabilties"]["repetitionpenalty_capable"] else "Repetition N/A", interactive=model_config_data["model_capabilties"]["repetitionpenalty_capable"]) + pitch_set_gr = gr.Slider(value=float(model_config_data["settings"]["pitch_set"]), minimum=-10, maximum=10, step=1, label="Pitch" if model_config_data["model_capabilties"]["pitch_capable"] else "Pitch N/A", interactive=model_config_data["model_capabilties"]["pitch_capable"]) + generationspeed_set_gr = gr.Slider(value=float(model_config_data["settings"]["generationspeed_set"]), minimum=0.25, maximum=2.00, step=0.25, label="Speed" if model_config_data["model_capabilties"]["generationspeed_capable"] else "Speed N/A", interactive=model_config_data["model_capabilties"]["generationspeed_capable"]) + with gr.Row(): + with gr.Column(): + gr.Markdown("### OpenAI Voice Mappings") + with gr.Group(): + with gr.Row(): + alloy_gr = gr.Dropdown(value=model_config_data["openai_voices"]["alloy"], label="Alloy", choices=voice_list, allow_custom_value=True) + echo_gr = gr.Dropdown(value=model_config_data["openai_voices"]["echo"], label="Echo", choices=voice_list, allow_custom_value=True) + with gr.Row(): + fable_gr = gr.Dropdown(value=model_config_data["openai_voices"]["fable"], label="Fable", choices=voice_list, allow_custom_value=True) + nova_gr = gr.Dropdown(value=model_config_data["openai_voices"]["nova"], label="Nova", choices=voice_list, allow_custom_value=True) + with gr.Row(): + onyx_gr = gr.Dropdown(value=model_config_data["openai_voices"]["onyx"], label="Onyx", choices=voice_list, allow_custom_value=True) + shimmer_gr = gr.Dropdown(value=model_config_data["openai_voices"]["shimmer"], label="Shimmer", choices=voice_list, allow_custom_value=True) + with gr.Column(): + gr.Markdown("### Default Voices") + with gr.Row(): + def_character_voice_gr = gr.Dropdown(value=model_config_data["settings"]["def_character_voice"], label="Default/Character Voice", choices=voice_list, allow_custom_value=True) + def_narrator_voice_gr = gr.Dropdown(value=model_config_data["settings"]["def_narrator_voice"], label="Narrator Voice", choices=voice_list, allow_custom_value=True) + with gr.Group(): + with gr.Row(): + details_text = gr.Textbox(label="Details", show_label=False, lines=5, interactive=False, value="In this section, you can set the default settings for this TTS engine. Settings that are not supported by the current engine will be greyed out and cannot be selected. Default voices specified here will be used when no specific voice is provided in the TTS generation request. If a voice is specified in the request, it will override these default settings. When using the OpenAI API compatable API with this TTS engine, the voice mappings will be applied. As the OpenAI API has a limited set of 6 voices, these mappings ensure compatibility by mapping the OpenAI voices to the available voices in this TTS engine.") + with gr.Row(): + submit_button = gr.Button("Update Settings") + output_message = gr.Textbox(label="Output Message", interactive=False, show_label=False) + submit_button.click(piper_model_update_settings, inputs=[def_character_voice_gr, def_narrator_voice_gr, lowvram_enabled_gr, deepspeed_enabled_gr, temperature_set_gr, repetitionpenalty_set_gr, pitch_set_gr, generationspeed_set_gr, alloy_gr, echo_gr, fable_gr, nova_gr, onyx_gr, shimmer_gr], outputs=output_message) + + ########################################################################################### + # Do not change this section apart from "TTS Engine Name" value to match your engine name # + ########################################################################################### + with gr.Tab("Engine Information"): + with gr.Row(): + with gr.Group(): + gr.Textbox(label="Manufacturer Name", value=model_config_data['model_details']['manufacturer_name'], interactive=False) + gr.Textbox(label="Manufacturer Website/TTS Engine Support", value=model_config_data['model_details']['manufacturer_website'], interactive=False) + gr.Textbox(label="Engine/Model Description", value=model_config_data['model_details']['model_description'], interactive=False, lines=13) + with gr.Column(): + with gr.Row(): + gr.Textbox(label="DeepSpeed Capable", value='Yes' if features_list['deepspeed_capable'] else 'No', interactive=False) + gr.Textbox(label="Pitch Capable", value='Yes' if features_list['pitch_capable'] else 'No', interactive=False) + gr.Textbox(label="Generation Speed Capable", value='Yes' if features_list['generationspeed_capable'] else 'No', interactive=False) + with gr.Row(): + gr.Textbox(label="Repetition Penalty Capable", value='Yes' if features_list['repetitionpenalty_capable'] else 'No', interactive=False) + gr.Textbox(label="Multi Languages Capable", value='Yes' if features_list['languages_capable'] else 'No', interactive=False) + gr.Textbox(label="Streaming Capable", value='Yes' if features_list['streaming_capable'] else 'No', interactive=False) + with gr.Row(): + gr.Textbox(label="Low VRAM Capable", value='Yes' if features_list['lowvram_capable'] else 'No', interactive=False) + gr.Textbox(label="Temperature Capable", value='Yes' if features_list['temperature_capable'] else 'No', interactive=False) + gr.Textbox(label="Multi Model Capable Engine", value='Yes' if features_list['multimodel_capable'] else 'No', interactive=False) + with gr.Row(): + gr.Textbox(label="Multi Voice Capable Models", value='Yes' if features_list['multivoice_capable'] else 'No', interactive=False) + gr.Textbox(label="Default Audio output format", value=model_config_data['model_capabilties']['audio_format'], interactive=False) + gr.Textbox(label="TTS Engine Name", value="piper", interactive=False) + with gr.Row(): + gr.Textbox(label="Windows Support", value='Yes' if features_list['windows_capable'] else 'No', interactive=False) + gr.Textbox(label="Linux Support", value='Yes' if features_list['linux_capable'] else 'No', interactive=False) + gr.Textbox(label="Mac Support", value='Yes' if features_list['mac_capable'] else 'No', interactive=False) + with gr.Row(): + gr.Markdown(""" + #### 🟧 DeepSpeed Capable + DeepSpeed is a deep learning optimization library that can significantly speed up model training and inference. If a model is DeepSpeed capable, it means it can utilize DeepSpeed to accelerate the generation of text-to-speech output. This requires the model to be loaded into CUDA/VRAM on an Nvidia GPU and the model's inference method to support DeepSpeed. + #### 🟧 Pitch Capable + Pitch refers to the highness or lowness of a sound. If a model is pitch capable, it can adjust the pitch of the generated speech, allowing for more expressive and varied output. + #### 🟧 Generation Speed Capable + Generation speed refers to the rate at which the model can generate text-to-speech output. If a model is generation speed capable, it means the speed of the generated speech can be adjusted, making it faster or slower depending on the desired output. + #### 🟧 Repetition Penalty Capable + Repetition penalty is a technique used to discourage the model from repeating the same words or phrases multiple times in the same sounding way. If a model is repetition penalty capable, it can apply this penalty during generation to improve the diversity and naturalness of the output. + #### 🟧 Multi-Languages Capable + Multi-language capable models can generate speech in multiple languages. This means that the model has been trained on data from different languages and can switch between them during generation. Some models are language-specific. + #### 🟧 Multi-Voice Capable + Multi-voice capable models generate speech in multiple voices or speaking styles. This means that the model has been trained on data from different speakers and can mimic their voices during generation, or is a voice cloning model that can generate speech based on the input sample. + """) + gr.Markdown(""" + #### 🟧 Streaming Capable + Streaming refers to the ability to generate speech output in real-time, without the need to generate the entire output before playback. If a model is streaming capable, it can generate speech on-the-fly, allowing for faster response times and more interactive applications. + #### 🟧 Low VRAM Capable + VRAM (Video Random Access Memory) is a type of memory used by GPUs to store and process data. If a model is low VRAM capable, it can efficiently utilize the available VRAM by moving data between CPU and GPU memory as needed, allowing for generation even on systems with limited VRAM where it may be competing with an LLM model for VRAM. + #### 🟧 Temperature Capable + Temperature is a hyperparameter that controls the randomness of the generated output. If a model is temperature capable, the temperature can be adjusted to make the output more or less random, affecting the creativity and variability of the generated speech. + #### 🟧 Multi-Model Capable Engine + If an engine is multi-model capable, it means that it can support and utilize multiple models for text-to-speech generation. This allows for greater flexibility and the ability to switch between different models depending on the desired output. Different models may be capable of different languages, specific languages, voices, etc. + #### 🟧 Default Audio Output Format + Specifies the file format in which the generated speech will be saved. Common audio formats include WAV, MP3, FLAC, Opus, AAC, and PCM. If you want different outputs, you can set the transcode function to change the output audio, though transcoding will add a little time to the generation and is not available for streaming generation. + #### 🟧 Windows/Linux/Mac Support + These indicators show whether the model and engine are compatible with Windows, Linux, or macOS. However, additional setup or requirements may be necessary to ensure full functionality on your operating system. Please note that full platform support has not been extensively tested. + """) + + ####################################################################################################################################################################################################### + # REQUIRED CHANGE # + # You will need to build a custom method to identify if your models are installed and download them. Store your models list in the available_models.json file, alongside your model_settings.json etc # + ####################################################################################################################################################################################################### + with gr.Tab("Models/Voices Download"): + with gr.Row(): + # Load the available models from the JSON file + with open(os.path.join(this_dir, "available_models.json"), "r") as f: + available_models = json.load(f) + # Extract the model names for the dropdown + model_names = [model["model_name"] for model in available_models["models"]] + # Create the dropdown + model_dropdown = gr.Dropdown(choices=sorted(model_names), label="Select Model", value=model_names[0]) + download_button = gr.Button("Download Model/Missing Files") + + def download_model(model_name, force_download=False): + # Find the selected model in the available models + selected_model = next(model for model in available_models["models"] if model["model_name"] == model_name) + # Get the folder path and files to download + folder_path = os.path.join(main_dir, "models", "piper") + files_to_download = selected_model["files_to_download"] + # Check if all files are already downloaded + all_files_exists = all(os.path.exists(os.path.join(folder_path, os.path.basename(url.split('?')[0]))) for url in files_to_download) + if all_files_exists and not force_download: + return "All files are already downloaded. No need to download again." + else: + # Create the folder if it doesn't exist + os.makedirs(folder_path, exist_ok=True) + # Download the missing files + for url in files_to_download: + file_name = os.path.basename(url.split('?')[0]) + file_path = os.path.join(folder_path, file_name) + if not os.path.exists(file_path) or force_download: + print(f"Downloading {file_name}...") + response = requests.get(url, stream=True) + total_size_in_bytes = int(response.headers.get("content-length", 0)) + block_size = 1024 # 1 Kibibyte + progress_bar = tqdm(total=total_size_in_bytes, unit="iB", unit_scale=True) + with open(file_path, "wb") as file: + for data in response.iter_content(block_size): + progress_bar.update(len(data)) + file.write(data) + progress_bar.close() + return "Model downloaded successfully!" + + with gr.Row(): + download_status = gr.Textbox(label="Download Status") + + download_button.click(download_model, inputs=model_dropdown, outputs=download_status) + + def show_confirm_cancel(model_name): + all_files_exists = all(os.path.exists(os.path.join(main_dir, "models", "piper", os.path.basename(file.split('?')[0]))) for model in available_models["models"] if model["model_name"] == model_name for file in model["files_to_download"]) + if all_files_exists: + return [gr.update(visible=True), gr.update(visible=True), gr.update(visible=True)] + else: + return [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)] + + def confirm_download(model_name): + download_status_text = download_model(model_name, force_download=True) + return [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), download_status_text] + + def cancel_download(): + return [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), ""] + + with gr.Row(): + confirm_button = gr.Button("Download Anyway", visible=False) + cancel_button = gr.Button("Cancel", visible=False) + + download_button.click(show_confirm_cancel, inputs=model_dropdown, outputs=[confirm_button, download_button, cancel_button]) + confirm_button.click(confirm_download, inputs=model_dropdown, outputs=[confirm_button, download_button, cancel_button, download_status]) + cancel_button.click(cancel_download, inputs=None, outputs=[confirm_button, download_button, cancel_button, download_status]) + + ################################################################################################### + # REQUIRED CHANGE # + # Add any engine specific help, bugs, issues, operating system specifc requirements/setup in here # + # Please use Markdown format, so gr.Markdown() with your markdown inside it. # + ################################################################################################### + with gr.Tab("Engine Help"): + with gr.Row(): + gr.Markdown(""" + ### 🟧 Where are the Piper models stored? + This extension will download the models to `/alltalk_tts/models/piper/` folder.
+ + ### 🟧 Where are the voices stored for piper models? + Piper models themselves are the voices/have the voices built into them. The models are made up of 2x files, an `onnx` file and a `onnx.json` file.
+ + ### 🟧 Where are the outputs stored & Automatic output wav file deletion + Voice outputs are stored in `/alltalk_tts/outputs/`. You can configure automatic maintenance deletion of old wav files by setting `Del WAV's older than` in the global settings.
+ + > When `Disabled`, your output wav files will be left untouched.
+ > When set to a setting `1 Day` or greater, your output wav files older than that time period will be automatically deleted on start-up of AllTalk.
+ """) + gr.Markdown(""" + """) + + + return app + +################################ +# REQUIRED CHANGE # +# Sets up the Gradio interface # +################################ +# This function sets up the Gradio interface for your TTS engine's settings page. +# You need to change the name of the function calls to match the names you set in the functions above. +# +# Specifically, you need to update the following: +# +# 1. The name of the function `xxxx_at_gradio_settings_page` to match your engine's name. +# For example, if your engine is named "mytts", the function should be named `mytts_at_gradio_settings_page`. +# +# 2. The name of the function call `xxxx_model_alltalk_settings(model_config_data)`. +# This should match the name you gave to the function that sets up the engine-specific settings page in Gradio. +# If you named that function `mytts_model_alltalk_settings`, then the call should be: +# `mytts_model_alltalk_settings(model_config_data)` +# +# After making these changes, this function will create and return the Gradio app for your TTS engine's settings page. +def piper_at_gradio_settings_page(model_config_data): + app = piper_model_alltalk_settings(model_config_data) + return app +def piper_at_gradio_settings_page(model_config_data): + app = piper_model_alltalk_settings(model_config_data) + return app \ No newline at end of file diff --git a/system/tts_engines/rvc/configs/config.py b/system/tts_engines/rvc/configs/config.py new file mode 100644 index 0000000..a585312 --- /dev/null +++ b/system/tts_engines/rvc/configs/config.py @@ -0,0 +1,170 @@ +import torch +import json +import os +from pathlib import Path + +version_config_list = [ + "v1/32000.json", + "v1/40000.json", + "v1/48000.json", + "v2/48000.json", + "v2/32000.json", +] + + +def singleton_variable(func): + def wrapper(*args, **kwargs): + if not wrapper.instance: + wrapper.instance = func(*args, **kwargs) + return wrapper.instance + + wrapper.instance = None + return wrapper + + +@singleton_variable +class Config: + def __init__(self): + self.device = "cuda:0" + self.is_half = True + self.use_jit = False + self.n_cpu = 0 + self.gpu_name = None + self.json_config = self.load_config_json() + self.gpu_mem = None + self.instead = "" + self.x_pad, self.x_query, self.x_center, self.x_max = self.device_config() + + @staticmethod + def load_config_json() -> dict: + d = {} + base_path = Path(__file__).resolve().parent + for config_file in version_config_list: + config_path = base_path / config_file + with open(config_path, "r") as f: + d[config_file] = json.load(f) + return d + + @staticmethod + def has_mps() -> bool: + if not torch.backends.mps.is_available(): + return False + try: + torch.zeros(1).to(torch.device("mps")) + return True + except Exception: + return False + + @staticmethod + def has_xpu() -> bool: + if hasattr(torch, "xpu") and torch.xpu.is_available(): + return True + else: + return False + + def use_fp32_config(self): + print( + f"Using FP32 config instead of FP16 due to GPU compatibility ({self.gpu_name})" + ) + for config_file in version_config_list: + config_path = os.path.join(os.getcwd(), "system", "tts_engines", "rvc", "configs", config_file) + with open(config_path, "r") as f: + config_data = f.read().replace("true", "false") + with open(config_path, "w") as f: + f.write(config_data) + + preprocess_path = os.path.join(os.getcwd(), "system", "tts_engines", "rvc", "train", "preprocess", "preprocess.py") + with open(preprocess_path, "r") as f: + preprocess_data = f.read().replace("3.7", "3.0") + with open(preprocess_path, "w") as f: + f.write(preprocess_data) + + def device_config(self) -> tuple: + if torch.cuda.is_available(): + if self.has_xpu(): + self.device = self.instead = "xpu:0" + self.is_half = True + i_device = int(self.device.split(":")[-1]) + self.gpu_name = torch.cuda.get_device_name(i_device) + if ( + ("16" in self.gpu_name and "V100" not in self.gpu_name.upper()) + or "P40" in self.gpu_name.upper() + or "P10" in self.gpu_name.upper() + or "1060" in self.gpu_name + or "1070" in self.gpu_name + or "1080" in self.gpu_name + ): + self.is_half = False + self.use_fp32_config() + self.gpu_mem = int( + torch.cuda.get_device_properties(i_device).total_memory + / 1024 + / 1024 + / 1024 + + 0.4 + ) + if self.gpu_mem <= 4: + preprocess_path = os.path.join(os.getcwd(), "system", "tts_engines", "rvc", "train", "preprocess", "preprocess.py") + with open(preprocess_path, "r") as f: + strr = f.read().replace("3.7", "3.0") + with open(preprocess_path, "w") as f: + f.write(strr) + elif self.has_mps(): + print("No supported Nvidia GPU found") + self.device = self.instead = "mps" + self.is_half = False + self.use_fp32_config() + else: + print("No supported Nvidia GPU found") + self.device = self.instead = "cpu" + self.is_half = False + self.use_fp32_config() + + if self.n_cpu == 0: + self.n_cpu = os.cpu_count() + + if self.is_half: + x_pad = 3 + x_query = 10 + x_center = 60 + x_max = 65 + else: + x_pad = 1 + x_query = 6 + x_center = 38 + x_max = 41 + + if self.gpu_mem is not None and self.gpu_mem <= 4: + x_pad = 1 + x_query = 5 + x_center = 30 + x_max = 32 + + return x_pad, x_query, x_center, x_max + + +def max_vram_gpu(gpu): + if torch.cuda.is_available(): + gpu_properties = torch.cuda.get_device_properties(gpu) + total_memory_gb = round(gpu_properties.total_memory / 1024 / 1024 / 1024) + return total_memory_gb + else: + return "0" + + +def get_gpu_info(): + ngpu = torch.cuda.device_count() + gpu_infos = [] + if torch.cuda.is_available() or ngpu != 0: + for i in range(ngpu): + gpu_name = torch.cuda.get_device_name(i) + mem = int( + torch.cuda.get_device_properties(i).total_memory / 1024 / 1024 / 1024 + + 0.4 + ) + gpu_infos.append("%s: %s %s GB" % (i, gpu_name, mem)) + if len(gpu_infos) > 0: + gpu_info = "\n".join(gpu_infos) + else: + gpu_info = "Unfortunately, there is no compatible GPU available to support your training." + return gpu_info diff --git a/system/tts_engines/rvc/configs/v1/32000.json b/system/tts_engines/rvc/configs/v1/32000.json new file mode 100644 index 0000000..d5f16d6 --- /dev/null +++ b/system/tts_engines/rvc/configs/v1/32000.json @@ -0,0 +1,46 @@ +{ + "train": { + "log_interval": 200, + "seed": 1234, + "epochs": 20000, + "learning_rate": 1e-4, + "betas": [0.8, 0.99], + "eps": 1e-9, + "batch_size": 4, + "fp16_run": true, + "lr_decay": 0.999875, + "segment_size": 12800, + "init_lr_ratio": 1, + "warmup_epochs": 0, + "c_mel": 45, + "c_kl": 1.0 + }, + "data": { + "max_wav_value": 32768.0, + "sampling_rate": 32000, + "filter_length": 1024, + "hop_length": 320, + "win_length": 1024, + "n_mel_channels": 80, + "mel_fmin": 0.0, + "mel_fmax": null + }, + "model": { + "inter_channels": 192, + "hidden_channels": 192, + "filter_channels": 768, + "n_heads": 2, + "n_layers": 6, + "kernel_size": 3, + "p_dropout": 0, + "resblock": "1", + "resblock_kernel_sizes": [3,7,11], + "resblock_dilation_sizes": [[1,3,5], [1,3,5], [1,3,5]], + "upsample_rates": [10,4,2,2,2], + "upsample_initial_channel": 512, + "upsample_kernel_sizes": [16,16,4,4,4], + "use_spectral_norm": false, + "gin_channels": 256, + "spk_embed_dim": 109 + } +} diff --git a/system/tts_engines/rvc/configs/v1/40000.json b/system/tts_engines/rvc/configs/v1/40000.json new file mode 100644 index 0000000..4ffc87b --- /dev/null +++ b/system/tts_engines/rvc/configs/v1/40000.json @@ -0,0 +1,46 @@ +{ + "train": { + "log_interval": 200, + "seed": 1234, + "epochs": 20000, + "learning_rate": 1e-4, + "betas": [0.8, 0.99], + "eps": 1e-9, + "batch_size": 4, + "fp16_run": true, + "lr_decay": 0.999875, + "segment_size": 12800, + "init_lr_ratio": 1, + "warmup_epochs": 0, + "c_mel": 45, + "c_kl": 1.0 + }, + "data": { + "max_wav_value": 32768.0, + "sampling_rate": 40000, + "filter_length": 2048, + "hop_length": 400, + "win_length": 2048, + "n_mel_channels": 125, + "mel_fmin": 0.0, + "mel_fmax": null + }, + "model": { + "inter_channels": 192, + "hidden_channels": 192, + "filter_channels": 768, + "n_heads": 2, + "n_layers": 6, + "kernel_size": 3, + "p_dropout": 0, + "resblock": "1", + "resblock_kernel_sizes": [3,7,11], + "resblock_dilation_sizes": [[1,3,5], [1,3,5], [1,3,5]], + "upsample_rates": [10,10,2,2], + "upsample_initial_channel": 512, + "upsample_kernel_sizes": [16,16,4,4], + "use_spectral_norm": false, + "gin_channels": 256, + "spk_embed_dim": 109 + } +} diff --git a/system/tts_engines/rvc/configs/v1/48000.json b/system/tts_engines/rvc/configs/v1/48000.json new file mode 100644 index 0000000..2d0e05b --- /dev/null +++ b/system/tts_engines/rvc/configs/v1/48000.json @@ -0,0 +1,46 @@ +{ + "train": { + "log_interval": 200, + "seed": 1234, + "epochs": 20000, + "learning_rate": 1e-4, + "betas": [0.8, 0.99], + "eps": 1e-9, + "batch_size": 4, + "fp16_run": true, + "lr_decay": 0.999875, + "segment_size": 11520, + "init_lr_ratio": 1, + "warmup_epochs": 0, + "c_mel": 45, + "c_kl": 1.0 + }, + "data": { + "max_wav_value": 32768.0, + "sampling_rate": 48000, + "filter_length": 2048, + "hop_length": 480, + "win_length": 2048, + "n_mel_channels": 128, + "mel_fmin": 0.0, + "mel_fmax": null + }, + "model": { + "inter_channels": 192, + "hidden_channels": 192, + "filter_channels": 768, + "n_heads": 2, + "n_layers": 6, + "kernel_size": 3, + "p_dropout": 0, + "resblock": "1", + "resblock_kernel_sizes": [3,7,11], + "resblock_dilation_sizes": [[1,3,5], [1,3,5], [1,3,5]], + "upsample_rates": [10,6,2,2,2], + "upsample_initial_channel": 512, + "upsample_kernel_sizes": [16,16,4,4,4], + "use_spectral_norm": false, + "gin_channels": 256, + "spk_embed_dim": 109 + } +} diff --git a/system/tts_engines/rvc/configs/v2/32000.json b/system/tts_engines/rvc/configs/v2/32000.json new file mode 100644 index 0000000..70e534f --- /dev/null +++ b/system/tts_engines/rvc/configs/v2/32000.json @@ -0,0 +1,46 @@ +{ + "train": { + "log_interval": 200, + "seed": 1234, + "epochs": 20000, + "learning_rate": 1e-4, + "betas": [0.8, 0.99], + "eps": 1e-9, + "batch_size": 4, + "fp16_run": true, + "lr_decay": 0.999875, + "segment_size": 12800, + "init_lr_ratio": 1, + "warmup_epochs": 0, + "c_mel": 45, + "c_kl": 1.0 + }, + "data": { + "max_wav_value": 32768.0, + "sampling_rate": 32000, + "filter_length": 1024, + "hop_length": 320, + "win_length": 1024, + "n_mel_channels": 80, + "mel_fmin": 0.0, + "mel_fmax": null + }, + "model": { + "inter_channels": 192, + "hidden_channels": 192, + "filter_channels": 768, + "n_heads": 2, + "n_layers": 6, + "kernel_size": 3, + "p_dropout": 0, + "resblock": "1", + "resblock_kernel_sizes": [3,7,11], + "resblock_dilation_sizes": [[1,3,5], [1,3,5], [1,3,5]], + "upsample_rates": [10,8,2,2], + "upsample_initial_channel": 512, + "upsample_kernel_sizes": [20,16,4,4], + "use_spectral_norm": false, + "gin_channels": 256, + "spk_embed_dim": 109 + } +} diff --git a/system/tts_engines/rvc/configs/v2/48000.json b/system/tts_engines/rvc/configs/v2/48000.json new file mode 100644 index 0000000..75f770c --- /dev/null +++ b/system/tts_engines/rvc/configs/v2/48000.json @@ -0,0 +1,46 @@ +{ + "train": { + "log_interval": 200, + "seed": 1234, + "epochs": 20000, + "learning_rate": 1e-4, + "betas": [0.8, 0.99], + "eps": 1e-9, + "batch_size": 4, + "fp16_run": true, + "lr_decay": 0.999875, + "segment_size": 17280, + "init_lr_ratio": 1, + "warmup_epochs": 0, + "c_mel": 45, + "c_kl": 1.0 + }, + "data": { + "max_wav_value": 32768.0, + "sampling_rate": 48000, + "filter_length": 2048, + "hop_length": 480, + "win_length": 2048, + "n_mel_channels": 128, + "mel_fmin": 0.0, + "mel_fmax": null + }, + "model": { + "inter_channels": 192, + "hidden_channels": 192, + "filter_channels": 768, + "n_heads": 2, + "n_layers": 6, + "kernel_size": 3, + "p_dropout": 0, + "resblock": "1", + "resblock_kernel_sizes": [3,7,11], + "resblock_dilation_sizes": [[1,3,5], [1,3,5], [1,3,5]], + "upsample_rates": [12,10,2,2], + "upsample_initial_channel": 512, + "upsample_kernel_sizes": [24,20,4,4], + "use_spectral_norm": false, + "gin_channels": 256, + "spk_embed_dim": 109 + } +} diff --git a/system/tts_engines/rvc/infer/infer.py b/system/tts_engines/rvc/infer/infer.py new file mode 100644 index 0000000..30e9539 --- /dev/null +++ b/system/tts_engines/rvc/infer/infer.py @@ -0,0 +1,350 @@ +import os +import sys +import time +import torch +import logging +import faiss +import numpy as np +import soundfile as sf +import librosa + +now_dir = os.getcwd() +sys.path.append(now_dir) + +from ..infer.pipeline import VC +from ..lib.utils import load_audio +from ..lib.tools.split_audio import process_audio, merge_audio +from ..lib.infer_pack.models import ( + SynthesizerTrnMs256NSFsid, + SynthesizerTrnMs256NSFsid_nono, + SynthesizerTrnMs768NSFsid, + SynthesizerTrnMs768NSFsid_nono, +) +from ..configs.config import Config +from ..lib.utils import load_embedding + +logging.getLogger("httpx").setLevel(logging.WARNING) +logging.getLogger("httpcore").setLevel(logging.WARNING) + +config = Config() +hubert_model = None +tgt_sr = None +net_g = None +vc = None +cpt = None +version = None +n_spk = None +debug_rvc = False + +def load_hubert(embedder_model): + global hubert_model + models, _, _ = load_embedding(embedder_model) + hubert_model = models[0] + hubert_model = hubert_model.to(config.device) + if config.is_half: + hubert_model = hubert_model.half() + else: + hubert_model = hubert_model.float() + hubert_model.eval() + +def voice_conversion( + sid=0, + input_audio_path=None, + f0_up_key=None, + f0_file=None, + f0_method=None, + file_index=None, + index_rate=None, + resample_sr=0, + rms_mix_rate=None, + protect=None, + hop_length=None, + output_path=None, + split_audio=False, + f0autotune=False, + filter_radius=None, + embedder_model=None, +): + global tgt_sr, net_g, vc, hubert_model, version + + f0_up_key = int(f0_up_key) + try: + print(f"Loading audio from {input_audio_path}") if debug_rvc else None + audio = load_audio(input_audio_path, 16000) + audio_max = np.abs(audio).max() / 0.95 + + if audio_max > 1: + audio /= audio_max + + if not hubert_model: + print(f"Loading hubert model with {embedder_model}") if debug_rvc else None + load_hubert(embedder_model) + if_f0 = cpt.get("f0", 1) + + file_index = ( + file_index.strip(" ") + .strip('"') + .strip("\n") + .strip('"') + .strip(" ") + .replace("trained", "added") + ) + if tgt_sr != resample_sr >= 16000: + tgt_sr = resample_sr + if split_audio == "True": + print("Splitting audio") if debug_rvc else None + result, new_dir_path = process_audio(input_audio_path) + if result == "Error": + return "Error with Split Audio", None + dir_path = ( + new_dir_path.strip(" ").strip('"').strip("\n").strip('"').strip(" ") + ) + if dir_path != "": + paths = [ + os.path.join(root, name) + for root, _, files in os.walk(dir_path, topdown=False) + for name in files + if name.endswith(".wav") and root == dir_path + ] + try: + for path in paths: + voice_conversion( + sid, + path, + f0_up_key, + None, + f0_method, + file_index, + index_rate, + resample_sr, + rms_mix_rate, + protect, + hop_length, + path, + False, + f0autotune, + embedder_model, + ) + except Exception as error: + print(f"Error processing segmented audio: {error}") + return f"Error {error}" + print("Finished processing segmented audio, now merging audio...") if debug_rvc else None + merge_timestamps_file = os.path.join( + os.path.dirname(new_dir_path), + f"{os.path.basename(input_audio_path).split('.')[0]}_timestamps.txt", + ) + tgt_sr, audio_opt = merge_audio(merge_timestamps_file) + os.remove(merge_timestamps_file) + + else: + print("Processing audio with VC pipeline") if debug_rvc else None + audio_opt = vc.pipeline( + hubert_model, + net_g, + sid, + audio, + input_audio_path, + f0_up_key, + f0_method, + file_index, + index_rate, + if_f0, + filter_radius, + tgt_sr, + resample_sr, + rms_mix_rate, + version, + protect, + hop_length, + f0autotune, + f0_file=f0_file, + ) + + # Resample the audio to the target sample rate before saving + if tgt_sr != resample_sr and resample_sr >= 16000: + print(f"Resampling audio from {tgt_sr} to {resample_sr}") if debug_rvc else None + audio_opt = librosa.resample(audio_opt, tgt_sr, resample_sr) + tgt_sr = resample_sr + + if output_path is not None: + print(f"Saving file to {output_path}") if debug_rvc else None + sf.write(output_path, audio_opt, tgt_sr, format="WAV") + print(f"File saved to {output_path}") if debug_rvc else None + + return (tgt_sr, audio_opt) + + except Exception as error: + print(f"Error during voice conversion: {error}") + return None, None + + + +def get_vc(weight_root, sid, file_index=None, training_data_size=10000, debug_rvc=False): + global n_spk, tgt_sr, net_g, vc, cpt, version + branding="AllTalk " + + if debug_rvc: + print(f"[{branding}Debug] Entering get_vc function") + print(f"[{branding}Debug] weight_root: {weight_root}") + print(f"[{branding}Debug] sid: {sid}") + print(f"[{branding}Debug] file_index: {file_index}") + print(f"[{branding}Debug] training_data_size: {training_data_size}") + + if sid == "" or sid == []: + global hubert_model + if hubert_model is not None: + if debug_rvc: + print(f"[{branding}Debug] Cleaning empty cache") + del net_g, n_spk, vc, hubert_model, tgt_sr + hubert_model = net_g = n_spk = vc = hubert_model = tgt_sr = None + if torch.cuda.is_available(): + torch.cuda.empty_cache() + if debug_rvc: + print(f"[{branding}Debug] sid is empty or an empty list") + + if_f0 = cpt.get("f0", 1) + version = cpt.get("version", "v1") + + if debug_rvc: + print(f"[{branding}Debug] Model version: {version}") + print(f"[{branding}Debug] f0: {if_f0}") + + if version == "v1": + if if_f0 == 1: + net_g = SynthesizerTrnMs256NSFsid(*cpt["config"], is_half=config.is_half) + else: + net_g = SynthesizerTrnMs256NSFsid_nono(*cpt["config"]) + elif version == "v2": + if if_f0 == 1: + net_g = SynthesizerTrnMs768NSFsid(*cpt["config"], is_half=config.is_half) + else: + net_g = SynthesizerTrnMs768NSFsid_nono(*cpt["config"]) + del net_g, cpt + if torch.cuda.is_available(): + torch.cuda.empty_cache() + cpt = None + + if debug_rvc: + print(f"[{branding}Debug] Loading model checkpoint") + + person = weight_root + cpt = torch.load(person, map_location="cpu") + tgt_sr = cpt["config"][-1] + cpt["config"][-3] = cpt["weight"]["emb_g.weight"].shape[0] + if_f0 = cpt.get("f0", 1) + version = cpt.get("version", "v1") + + if debug_rvc: + print(f"[{branding}Debug] Model version: {version}") + + if version == "v1": + if if_f0 == 1: + net_g = SynthesizerTrnMs256NSFsid(*cpt["config"], is_half=config.is_half) + else: + net_g = SynthesizerTrnMs256NSFsid_nono(*cpt["config"]) + elif version == "v2": + if if_f0 == 1: + net_g = SynthesizerTrnMs768NSFsid(*cpt["config"], is_half=config.is_half) + else: + net_g = SynthesizerTrnMs768NSFsid_nono(*cpt["config"]) + net_g.load_state_dict(cpt["weight"], strict=False) + net_g.eval().to(config.device) + if config.is_half: + net_g = net_g.half() + else: + net_g = net_g.float() + + if file_index is not None: + if debug_rvc: + print(f"[{branding}Debug] Loading index file: {file_index}") + index_file = file_index + index = faiss.read_index(index_file) + if debug_rvc: + print(f"[{branding}Debug] Extracting data from index") + data = index.reconstruct_n(0, index.ntotal) + d = index.d + nlist = index.nlist + + training_data_size = min(training_data_size, len(data)) + train_data = data[:training_data_size] + + else: + if debug_rvc: + print(f"[{branding}Debug] No index file provided") + data = None + d = None + nlist = None + train_data = None + + if debug_rvc: + print(f"[{branding}Debug] Creating VC instance") + + if data is not None and d is not None and nlist is not None and train_data is not None: + if debug_rvc: + print(f"[{branding}Debug] Data: {data}") + print(f"[{branding}Debug] Dimensionality: {d}") + print(f"[{branding}Debug] Number of centroids: {nlist}") + print(f"[{branding}Debug] Training data size: {len(train_data)}") + vc = VC(tgt_sr, config, data, d, train_data, debug_rvc) + else: + vc = VC(tgt_sr, config) + + n_spk = cpt["config"][-3] + if debug_rvc: + print(f"[{branding}Debug] Leaving get_vc function") + return vc + + +def infer_pipeline( + f0up_key, + filter_radius, + index_rate, + rms_mix_rate, + protect, + hop_length, + f0method, + audio_input_path, + audio_output_path, + model_path, + index_path, + split_audio, + f0autotune, + embedder_model, + training_data_size, + debug_rvc, +): + global tgt_sr, net_g, vc, cpt + debug_rvc = debug_rvc + if index_path is None or index_path.strip() == "": + file_index = None + else: + file_index = index_path + get_vc(model_path, 0, file_index, training_data_size, debug_rvc) + + try: + start_time = time.time() + voice_conversion( + sid=0, + input_audio_path=audio_input_path, + f0_up_key=f0up_key, + f0_file=None, + f0_method=f0method, + file_index=index_path, + index_rate=float(index_rate), + rms_mix_rate=float(rms_mix_rate), + protect=float(protect), + hop_length=hop_length, + output_path=audio_output_path, + split_audio=split_audio, + f0autotune=f0autotune, + filter_radius=filter_radius, + embedder_model=embedder_model, + ) + + end_time = time.time() + elapsed_time = end_time - start_time + print(f"Conversion completed. Output file: '{audio_output_path}' in {elapsed_time:.2f} seconds.") if debug_rvc else None + + except Exception as error: + print(f"Voice conversion failed: {error}") + diff --git a/system/tts_engines/rvc/infer/pipeline.py b/system/tts_engines/rvc/infer/pipeline.py new file mode 100644 index 0000000..c344cde --- /dev/null +++ b/system/tts_engines/rvc/infer/pipeline.py @@ -0,0 +1,740 @@ +import numpy as np +import torch +import sys +import os +import pdb +import parselmouth +from time import time as ttime +import torch.nn.functional as F +import torchcrepe +from torch import Tensor +import scipy.signal as signal +import pyworld, os, faiss, librosa, torchcrepe +from scipy import signal +from functools import lru_cache +import random +import gc +import re +import traceback +from pathlib import Path +# Get the current working directory +script_dir = Path(__file__).resolve().parent +now_dir = script_dir.parents[3] + +sys.path.append(now_dir) +from ..lib.FCPEF0Predictor import FCPEF0Predictor + +bh, ah = signal.butter(N=5, Wn=48, btype="high", fs=16000) + +input_audio_path2wav = {} + + +@lru_cache +def cache_harvest_f0(input_audio_path, fs, f0max, f0min, frame_period): + audio = input_audio_path2wav[input_audio_path] + f0, t = pyworld.harvest( + audio, + fs=fs, + f0_ceil=f0max, + f0_floor=f0min, + frame_period=frame_period, + ) + f0 = pyworld.stonemask(audio, f0, t, fs) + return f0 + + +def change_rms(data1, sr1, data2, sr2, rate): + # print(data1.max(),data2.max()) + rms1 = librosa.feature.rms(y=data1, frame_length=sr1 // 2 * 2, hop_length=sr1 // 2) + rms2 = librosa.feature.rms(y=data2, frame_length=sr2 // 2 * 2, hop_length=sr2 // 2) + + rms1 = torch.from_numpy(rms1) + rms1 = F.interpolate( + rms1.unsqueeze(0), size=data2.shape[0], mode="linear" + ).squeeze() + + rms2 = torch.from_numpy(rms2) + rms2 = F.interpolate( + rms2.unsqueeze(0), size=data2.shape[0], mode="linear" + ).squeeze() + rms2 = torch.max(rms2, torch.zeros_like(rms2) + 1e-6) + + data2 *= ( + torch.pow(rms1, torch.tensor(1 - rate)) + * torch.pow(rms2, torch.tensor(rate - 1)) + ).numpy() + return data2 + +from sklearn.metrics import ( + silhouette_score, + calinski_harabasz_score, + davies_bouldin_score, + fowlkes_mallows_score, + adjusted_rand_score, + adjusted_mutual_info_score +) + +class VC(object): + def __init__(self, tgt_sr, config, data=None, d=None, train_data=None, debug_rvc=False): + branding = "AllTalk " + self.x_pad, self.x_query, self.x_center, self.x_max, self.is_half = ( + config.x_pad, + config.x_query, + config.x_center, + config.x_max, + config.is_half, + ) + self.sr = 16000 + self.window = 160 + self.t_pad = self.sr * self.x_pad + self.t_pad_tgt = tgt_sr * self.x_pad + self.t_pad2 = self.t_pad * 2 + self.t_query = self.sr * self.x_query + self.t_center = self.sr * self.x_center + self.t_max = self.sr * self.x_max + self.device = config.device + self.ref_freqs = [ + 65.41, + 82.41, + 110.00, + 146.83, + 196.00, + 246.94, + 329.63, + 440.00, + 587.33, + 783.99, + 1046.50, + ] + self.note_dict = self.generate_interpolated_frequencies() + + if data is not None and d is not None and train_data is not None: + if debug_rvc: + print(f"[{branding}Debug] Initializing FAISS quantizer with dimensionality: {d}") + self.quantizer = faiss.IndexFlatL2(d) + if debug_rvc: + print(f"[{branding}Debug] FAISS quantizer initialized: {self.quantizer}") + + # Dynamic adjustment of new_nlist + new_nlist = max(1, len(train_data) // 39) # Ensure at least 1 + if debug_rvc: + print(f"[{branding}Debug] Using nlist: {new_nlist}") + self.index = faiss.IndexIVFFlat(self.quantizer, d, new_nlist) + if debug_rvc: + print(f"[{branding}Debug] FAISS IndexIVFFlat initialized: {self.index}") + + if debug_rvc: + print(f"[{branding}Debug] Training FAISS index with {len(train_data)} training points and {new_nlist} centroids") + self.index.train(train_data) + if debug_rvc: + print(f"[{branding}Debug] FAISS index trained") + + if debug_rvc: + print(f"[{branding}Debug] Adding {len(data)} data points to FAISS index") + self.index.add(data) + if debug_rvc: + print(f"[{branding}Debug] Data added to FAISS index") + + else: + self.quantizer = None + self.index = None + if debug_rvc: + print(f"[{branding}Debug] FAISS index not initialized due to missing data.") + + + def generate_interpolated_frequencies(self): + note_dict = [] + for i in range(len(self.ref_freqs) - 1): + freq_low = self.ref_freqs[i] + freq_high = self.ref_freqs[i + 1] + interpolated_freqs = np.linspace(freq_low, freq_high, num=10, endpoint=False) + note_dict.extend(interpolated_freqs) + note_dict.append(self.ref_freqs[-1]) + return note_dict + + + def autotune_f0(self, f0): + # Autotunes the given fundamental frequency (f0) to the nearest musical note. + autotuned_f0 = np.zeros_like(f0) + for i, freq in enumerate(f0): + # Find the closest note + closest_note = min(self.note_dict, key=lambda x: abs(x - freq)) + autotuned_f0[i] = closest_note + return autotuned_f0 + + def get_optimal_torch_device(self, index: int = 0) -> torch.device: + #print("GETTING OPTIMAL DEVICE") + if torch.cuda.is_available(): + #print("GETTING OPTIMAL DEVICE CUDA") + return torch.device(f"cuda:{index % torch.cuda.device_count()}") + elif torch.backends.mps.is_available(): + #print("GETTING OPTIMAL DEVICE MPS") + return torch.device("mps") + #print("GETTING OPTIMAL DEVICE CPU") + return torch.device("cpu") + + def get_f0_crepe_computation( + self, + x, + f0_min, + f0_max, + p_len, + hop_length, + model="full", + ): + x = x.astype(np.float32) + x /= np.quantile(np.abs(x), 0.999) + torch_device = self.get_optimal_torch_device() + audio = torch.from_numpy(x).to(torch_device, copy=True) + audio = torch.unsqueeze(audio, dim=0) + if audio.ndim == 2 and audio.shape[0] > 1: + audio = torch.mean(audio, dim=0, keepdim=True).detach() + audio = audio.detach() + pitch: Tensor = torchcrepe.predict( + audio, + self.sr, + hop_length, + f0_min, + f0_max, + model, + batch_size=hop_length * 2, + device=torch_device, + pad=True, + ) + p_len = p_len or x.shape[0] // hop_length + source = np.array(pitch.squeeze(0).cpu().float().numpy()) + source[source < 0.001] = np.nan + target = np.interp( + np.arange(0, len(source) * p_len, len(source)) / p_len, + np.arange(0, len(source)), + source, + ) + f0 = np.nan_to_num(target) + #print("LEAVING get_f0_crepe_computation") + return f0 + + def get_f0_official_crepe_computation( + self, + x, + f0_min, + f0_max, + model="full", + ): + batch_size = 512 + audio = torch.tensor(np.copy(x))[None].float() + f0, pd = torchcrepe.predict( + audio, + self.sr, + self.window, + f0_min, + f0_max, + model, + batch_size=batch_size, + device=self.device, + return_periodicity=True, + ) + pd = torchcrepe.filter.median(pd, 3) + f0 = torchcrepe.filter.mean(f0, 3) + f0[pd < 0.1] = 0 + f0 = f0[0].cpu().numpy() + #print("LEAVING get_f0_official_crepe_computation") + return f0 + + def get_f0_hybrid_computation( + self, + methods_str, + x, + f0_min, + f0_max, + p_len, + hop_length, + ): + methods_str = re.search("hybrid\[(.+)\]", methods_str) + if methods_str: + methods = [method.strip() for method in methods_str.group(1).split("+")] + f0_computation_stack = [] + #print(f"Calculating f0 pitch estimations for methods {str(methods)}") + x = x.astype(np.float32) + x /= np.quantile(np.abs(x), 0.999) + for method in methods: + f0 = None + if method == "crepe": + f0 = self.get_f0_crepe_computation( + x, f0_min, f0_max, p_len, int(hop_length) + ) + elif method == "rmvpe": + if hasattr(self, "model_rmvpe") == False: + from ..lib.rmvpe import RMVPE + model_path = os.path.join(now_dir, "models", "rvc_base", "rmvpe.pt") + self.model_rmvpe = RMVPE( + model_path, is_half=self.is_half, device=self.device + ) + f0 = self.model_rmvpe.infer_from_audio(x, thred=0.03) + f0 = f0[1:] + elif method == "fcpe": + model_path = os.path.join(now_dir, "models", "rvc_base", "fcpe.pt") + self.model_fcpe = FCPEF0Predictor( + model_path, + f0_min=int(f0_min), + f0_max=int(f0_max), + dtype=torch.float32, + device=self.device, + sampling_rate=self.sr, + threshold=0.03, + ) + f0 = self.model_fcpe.compute_f0(x, p_len=p_len) + del self.model_fcpe + gc.collect() + f0_computation_stack.append(f0) + + #print(f"Calculating hybrid median f0 from the stack of {str(methods)}") + f0_computation_stack = [fc for fc in f0_computation_stack if fc is not None] + f0_median_hybrid = None + if len(f0_computation_stack) == 1: + f0_median_hybrid = f0_computation_stack[0] + else: + f0_median_hybrid = np.nanmedian(f0_computation_stack, axis=0) + #print("LEAVING get_f0_hybrid_computation") + return f0_median_hybrid + + def get_f0( + self, + input_audio_path, + x, + p_len, + f0_up_key, + f0_method, + filter_radius, + hop_length, + f0autotune, + inp_f0=None, + ): + global input_audio_path2wav + time_step = self.window / self.sr * 1000 + f0_min = 50 + f0_max = 1100 + f0_mel_min = 1127 * np.log(1 + f0_min / 700) + f0_mel_max = 1127 * np.log(1 + f0_max / 700) + if f0_method == "pm": + f0 = ( + parselmouth.Sound(x, self.sr) + .to_pitch_ac( + time_step=time_step / 1000, + voicing_threshold=0.6, + pitch_floor=f0_min, + pitch_ceiling=f0_max, + ) + .selected_array["frequency"] + ) + pad_size = (p_len - len(f0) + 1) // 2 + if pad_size > 0 or p_len - len(f0) - pad_size > 0: + f0 = np.pad( + f0, [[pad_size, p_len - len(f0) - pad_size]], mode="constant" + ) + elif f0_method == "harvest": + input_audio_path2wav[input_audio_path] = x.astype(np.double) + f0 = cache_harvest_f0(input_audio_path, self.sr, f0_max, f0_min, 10) + if int(filter_radius) > 2: + f0 = signal.medfilt(f0, 3) + elif f0_method == "dio": + f0, t = pyworld.dio( + x.astype(np.double), + fs=self.sr, + f0_ceil=f0_max, + f0_floor=f0_min, + frame_period=10, + ) + f0 = pyworld.stonemask(x.astype(np.double), f0, t, self.sr) + f0 = signal.medfilt(f0, 3) + elif f0_method == "crepe": + f0 = self.get_f0_crepe_computation( + x, f0_min, f0_max, p_len, int(hop_length) + ) + elif f0_method == "crepe-tiny": + f0 = self.get_f0_crepe_computation( + x, f0_min, f0_max, p_len, int(hop_length), "tiny" + ) + elif f0_method == "rmvpe": + if hasattr(self, "model_rmvpe") == False: + from ..lib.rmvpe import RMVPE + model_path = os.path.join(now_dir, "models", "rvc_base", "rmvpe.pt") + self.model_rmvpe = RMVPE( + model_path, is_half=self.is_half, device=self.device + ) + f0 = self.model_rmvpe.infer_from_audio(x, thred=0.03) + elif f0_method == "fcpe": + model_path = os.path.join(now_dir, "models", "rvc_base", "fcpe.pt") + self.model_fcpe = FCPEF0Predictor( + model_path, + f0_min=int(f0_min), + f0_max=int(f0_max), + dtype=torch.float32, + device=self.device, + sampling_rate=self.sr, + threshold=0.03, + ) + f0 = self.model_fcpe.compute_f0(x, p_len=p_len) + del self.model_fcpe + gc.collect() + elif "hybrid" in f0_method: + input_audio_path2wav[input_audio_path] = x.astype(np.double) + f0 = self.get_f0_hybrid_computation( + f0_method, + x, + f0_min, + f0_max, + p_len, + hop_length, + ) + + if f0autotune == "True": + f0 = self.autotune_f0(f0) + + f0 *= pow(2, f0_up_key / 12) + tf0 = self.sr // self.window + if inp_f0 is not None: + delta_t = np.round( + (inp_f0[:, 0].max() - inp_f0[:, 0].min()) * tf0 + 1 + ).astype("int16") + replace_f0 = np.interp( + list(range(delta_t)), inp_f0[:, 0] * 100, inp_f0[:, 1] + ) + shape = f0[self.x_pad * tf0 : self.x_pad * tf0 + len(replace_f0)].shape[0] + f0[self.x_pad * tf0 : self.x_pad * tf0 + len(replace_f0)] = replace_f0[ + :shape + ] + f0bak = f0.copy() + f0_mel = 1127 * np.log(1 + f0 / 700) + f0_mel[f0_mel > 0] = (f0_mel[f0_mel > 0] - f0_mel_min) * 254 / ( + f0_mel_max - f0_mel_min + ) + 1 + f0_mel[f0_mel <= 1] = 1 + f0_mel[f0_mel > 255] = 255 + f0_coarse = np.rint(f0_mel).astype(int) + #print("LEAVING get_f0") + return f0_coarse, f0bak + + def vc( + self, + model, + net_g, + sid, + audio0, + pitch, + pitchf, + index, + big_npy, + index_rate, + version, + protect, + ): + #print("ENTERING pipeline vc") + #print(f"audio0 shape: {audio0.shape}") + feats = torch.from_numpy(audio0) + #print(f"feats initial dtype: {feats.dtype}") + if self.is_half: + feats = feats.half() + #print("Converting feats to half precision") + else: + feats = feats.float() + #print("Converting feats to float precision") + #print(f"feats dtype after conversion: {feats.dtype}") + if feats.dim() == 2: + #print("feats has 2 dimensions, taking mean along last dimension") + feats = feats.mean(-1) + assert feats.dim() == 1, feats.dim() + #print(f"feats shape after mean: {feats.shape}") + feats = feats.view(1, -1) + #print(f"feats shape after view: {feats.shape}") + padding_mask = torch.BoolTensor(feats.shape).to(self.device).fill_(False) + #print(f"padding_mask shape: {padding_mask.shape}") + #print(f"padding_mask device: {padding_mask.device}") + + inputs = { + "source": feats.to(self.device), + "padding_mask": padding_mask, + "output_layer": 9 if version == "v1" else 12, + } + #print(f"inputs source shape: {inputs['source'].shape}") + #print(f"inputs source device: {inputs['source'].device}") + #print(f"output_layer: {inputs['output_layer']}") + t0 = ttime() + #print("Pipeline vc STEP 1:") + with torch.no_grad(): + logits = model.extract_features(**inputs) + #print(f"logits type: {type(logits)}") + #print(f"logits length: {len(logits)}") + #print(f"logits[0] shape: {logits[0].shape}") + feats = model.final_proj(logits[0]) if version == "v1" else logits[0] + #print(f"feats shape after final_proj or logits[0]: {feats.shape}") + #print("Pipeline vc STEP 1a:") + if protect < 0.5 and pitch is not None and pitchf is not None: + #print("Cloning feats to feats0") + feats0 = feats.clone() + #print(f"feats0 shape: {feats0.shape}") + #print("Pipeline vc STEP 1b:") + if ( + isinstance(index, type(None)) == False + and isinstance(big_npy, type(None)) == False + and index_rate != 0 + ): + #print("Pipeline vc STEP 1c:") + #print(f"feats shape: {feats.shape}") + npy = feats[0, :feats.shape[1], :].cpu().numpy() + #print(f"npy shape: {npy.shape}") + #print("Pipeline vc STEP 1c1:") + if self.is_half: + #print("Pipeline vc STEP 1c2:") + npy = npy.astype("float32") + #print(f"Converted npy to float32. npy dtype: {npy.dtype}") + #print("Pipeline vc STEP 1c3:") + #print(f"npy size: {npy.size}") + + score, ix = self.index.search(npy, k=4) + + #print(f"score shape: {score.shape}") + #print(f"ix shape: {ix.shape}") + #print("Pipeline vc STEP 1c4:") + weight = np.square(1 / score) + #print(f"weight shape: {weight.shape}") + #print("Pipeline vc STEP 1c5:") + weight /= weight.sum(axis=1, keepdims=True) + #print(f"weight shape after normalization: {weight.shape}") + #print("Pipeline vc STEP 1d:") + npy = np.sum(big_npy[ix] * np.expand_dims(weight, axis=2), axis=1) + #print(f"npy shape after weighted sum: {npy.shape}") + #print("Pipeline vc STEP 1e:") + if self.is_half: + npy = npy.astype("float16") + #print(f"Converted npy to float16. npy dtype: {npy.dtype}") + #print("Pipeline vc STEP 1f:") + feats = ( + torch.from_numpy(npy).unsqueeze(0).to(self.device) * index_rate + + (1 - index_rate) * feats + ) + #print(f"feats shape after updating with npy: {feats.shape}") + #print("Pipeline vc STEP 2:") + feats = F.interpolate(feats.permute(0, 2, 1), scale_factor=2).permute(0, 2, 1) + #print(f"feats shape after interpolation: {feats.shape}") + if protect < 0.5 and pitch is not None and pitchf is not None: + feats0 = F.interpolate(feats0.permute(0, 2, 1), scale_factor=2).permute(0, 2, 1) + #print(f"feats0 shape after interpolation: {feats0.shape}") + t1 = ttime() + p_len = audio0.shape[0] // self.window + #print(f"Initial p_len: {p_len}") + if feats.shape[1] < p_len: + p_len = feats.shape[1] + #print(f"Updated p_len to match feats shape: {p_len}") + if pitch is not None and pitchf is not None: + pitch = pitch[:, :p_len] + pitchf = pitchf[:, :p_len] + #print(f"Updated pitch shape: {pitch.shape}") + #print(f"Updated pitchf shape: {pitchf.shape}") + #print("Pipeline vc STEP 3:") + if protect < 0.5 and pitch is not None and pitchf is not None: + pitchff = pitchf.clone() + #print(f"pitchff shape: {pitchff.shape}") + pitchff[pitchf > 0] = 1 + pitchff[pitchf < 1] = protect + pitchff = pitchff.unsqueeze(-1) + #print(f"pitchff shape after unsqueeze: {pitchff.shape}") + feats = feats * pitchff + feats0 * (1 - pitchff) + #print(f"feats shape after updating with pitchff: {feats.shape}") + feats = feats.to(feats0.dtype) + #print(f"feats dtype after conversion: {feats.dtype}") + p_len = torch.tensor([p_len], device=self.device).long() + #print(f"p_len tensor: {p_len}") + #print(f"p_len device: {p_len.device}") + #print("Pipeline vc p_len is:", p_len) + with torch.no_grad(): + if pitch is not None and pitchf is not None: + audio1 = ( + (net_g.infer(feats, p_len, pitch, pitchf, sid)[0][0, 0]) + .data.cpu() + .float() + .numpy() + ) + else: + audio1 = ( + (net_g.infer(feats, p_len, sid)[0][0, 0]).data.cpu().float().numpy() + ) + #print(f"audio1 shape: {audio1.shape}") + del feats, p_len, padding_mask + if torch.cuda.is_available(): + torch.cuda.empty_cache() + t2 = ttime() + #print(f"Pipeline vc time breakdown:") + #print(f" Step 1: {t1 - t0:.3f}s") + #print(f" Step 2: {t2 - t1:.3f}s") + #print(f" Total: {t2 - t0:.3f}s") + #print("Leaving pipeline vc") + return audio1 + + def pipeline( + self, + model, + net_g, + sid, + audio, + input_audio_path, + f0_up_key, + f0_method, + file_index, + index_rate, + if_f0, + filter_radius, + tgt_sr, + resample_sr, + rms_mix_rate, + version, + protect, + hop_length, + f0autotune, + f0_file=None, + ): + if file_index != "" and os.path.exists(file_index) == True and index_rate != 0: + try: + index = faiss.read_index(file_index) + big_npy = index.reconstruct_n(0, index.ntotal) + except Exception as error: + print(error) + index = big_npy = None + else: + index = big_npy = None + audio = signal.filtfilt(bh, ah, audio) + audio_pad = np.pad(audio, (self.window // 2, self.window // 2), mode="reflect") + opt_ts = [] + if audio_pad.shape[0] > self.t_max: + audio_sum = np.zeros_like(audio) + for i in range(self.window): + audio_sum += audio_pad[i : i - self.window] + for t in range(self.t_center, audio.shape[0], self.t_center): + opt_ts.append( + t + - self.t_query + + np.where( + np.abs(audio_sum[t - self.t_query : t + self.t_query]) + == np.abs(audio_sum[t - self.t_query : t + self.t_query]).min() + )[0][0] + ) + s = 0 + audio_opt = [] + t = None + t1 = ttime() + audio_pad = np.pad(audio, (self.t_pad, self.t_pad), mode="reflect") + p_len = audio_pad.shape[0] // self.window + inp_f0 = None + if hasattr(f0_file, "name") == True: + try: + with open(f0_file.name, "r") as f: + lines = f.read().strip("\n").split("\n") + inp_f0 = [] + for line in lines: + inp_f0.append([float(i) for i in line.split(",")]) + inp_f0 = np.array(inp_f0, dtype="float32") + except Exception as error: + print(error) + sid = torch.tensor(sid, device=self.device).unsqueeze(0).long() + pitch, pitchf = None, None + if if_f0 == 1: + pitch, pitchf = self.get_f0( + input_audio_path, + audio_pad, + p_len, + f0_up_key, + f0_method, + filter_radius, + hop_length, + f0autotune, + inp_f0, + ) + pitch = pitch[:p_len] + pitchf = pitchf[:p_len] + if self.device == "mps": + pitchf = pitchf.astype(np.float32) + pitch = torch.tensor(pitch, device=self.device).unsqueeze(0).long() + pitchf = torch.tensor(pitchf, device=self.device).unsqueeze(0).float() + t2 = ttime() + for t in opt_ts: + t = t // self.window * self.window + if if_f0 == 1: + audio_opt.append( + self.vc( + model, + net_g, + sid, + audio_pad[s : t + self.t_pad2 + self.window], + pitch[:, s // self.window : (t + self.t_pad2) // self.window], + pitchf[:, s // self.window : (t + self.t_pad2) // self.window], + index, + big_npy, + index_rate, + version, + protect, + )[self.t_pad_tgt : -self.t_pad_tgt] + ) + else: + audio_opt.append( + self.vc( + model, + net_g, + sid, + audio_pad[s : t + self.t_pad2 + self.window], + None, + None, + index, + big_npy, + index_rate, + version, + protect, + )[self.t_pad_tgt : -self.t_pad_tgt] + ) + s = t + if if_f0 == 1: + audio_opt.append( + self.vc( + model, + net_g, + sid, + audio_pad[t:], + pitch[:, t // self.window :] if t is not None else pitch, + pitchf[:, t // self.window :] if t is not None else pitchf, + index, + big_npy, + index_rate, + version, + protect, + )[self.t_pad_tgt : -self.t_pad_tgt] + ) + else: + audio_opt.append( + self.vc( + model, + net_g, + sid, + audio_pad[t:], + None, + None, + index, + big_npy, + index_rate, + version, + protect, + )[self.t_pad_tgt : -self.t_pad_tgt] + ) + audio_opt = np.concatenate(audio_opt) + if rms_mix_rate != 1: + audio_opt = change_rms(audio, 16000, audio_opt, tgt_sr, rms_mix_rate) + if resample_sr >= 16000 and tgt_sr != resample_sr: + audio_opt = librosa.resample( + audio_opt, orig_sr=tgt_sr, target_sr=resample_sr + ) + audio_max = np.abs(audio_opt).max() / 0.99 + max_int16 = 32768 + if audio_max > 1: + max_int16 /= audio_max + audio_opt = (audio_opt * max_int16).astype(np.int16) + del pitch, pitchf, sid + if torch.cuda.is_available(): + torch.cuda.empty_cache() + return audio_opt \ No newline at end of file diff --git a/system/tts_engines/rvc/lib/FCPEF0Predictor.py b/system/tts_engines/rvc/lib/FCPEF0Predictor.py new file mode 100644 index 0000000..08541bb --- /dev/null +++ b/system/tts_engines/rvc/lib/FCPEF0Predictor.py @@ -0,0 +1,1036 @@ +from typing import Union + +import torch.nn.functional as F +import numpy as np +import torch +import torch.nn as nn +from torch.nn.utils.parametrizations import weight_norm +from torchaudio.transforms import Resample +import os +import librosa +import soundfile as sf +import torch.utils.data +from librosa.filters import mel as librosa_mel_fn +import math +from functools import partial + +from einops import rearrange, repeat +from local_attention import LocalAttention +from torch import nn + +os.environ["LRU_CACHE_CAPACITY"] = "3" + + +def load_wav_to_torch(full_path, target_sr=None, return_empty_on_exception=False): + sampling_rate = None + try: + data, sampling_rate = sf.read(full_path, always_2d=True) # than soundfile. + except Exception as error: + print(f"'{full_path}' failed to load with {error}") + if return_empty_on_exception: + return [], sampling_rate or target_sr or 48000 + else: + raise Exception(error) + + if len(data.shape) > 1: + data = data[:, 0] + assert ( + len(data) > 2 + ) # check duration of audio file is > 2 samples (because otherwise the slice operation was on the wrong dimension) + + if np.issubdtype(data.dtype, np.integer): # if audio data is type int + max_mag = -np.iinfo( + data.dtype + ).min # maximum magnitude = min possible value of intXX + else: # if audio data is type fp32 + max_mag = max(np.amax(data), -np.amin(data)) + max_mag = ( + (2**31) + 1 + if max_mag > (2**15) + else ((2**15) + 1 if max_mag > 1.01 else 1.0) + ) # data should be either 16-bit INT, 32-bit INT or [-1 to 1] float32 + + data = torch.FloatTensor(data.astype(np.float32)) / max_mag + + if ( + torch.isinf(data) | torch.isnan(data) + ).any() and return_empty_on_exception: # resample will crash with inf/NaN inputs. return_empty_on_exception will return empty arr instead of except + return [], sampling_rate or target_sr or 48000 + if target_sr is not None and sampling_rate != target_sr: + data = torch.from_numpy( + librosa.core.resample( + data.numpy(), orig_sr=sampling_rate, target_sr=target_sr + ) + ) + sampling_rate = target_sr + + return data, sampling_rate + + +def dynamic_range_compression(x, C=1, clip_val=1e-5): + return np.log(np.clip(x, a_min=clip_val, a_max=None) * C) + + +def dynamic_range_decompression(x, C=1): + return np.exp(x) / C + + +def dynamic_range_compression_torch(x, C=1, clip_val=1e-5): + return torch.log(torch.clamp(x, min=clip_val) * C) + + +def dynamic_range_decompression_torch(x, C=1): + return torch.exp(x) / C + + +class STFT: + def __init__( + self, + sr=22050, + n_mels=80, + n_fft=1024, + win_size=1024, + hop_length=256, + fmin=20, + fmax=11025, + clip_val=1e-5, + ): + self.target_sr = sr + + self.n_mels = n_mels + self.n_fft = n_fft + self.win_size = win_size + self.hop_length = hop_length + self.fmin = fmin + self.fmax = fmax + self.clip_val = clip_val + self.mel_basis = {} + self.hann_window = {} + + def get_mel(self, y, keyshift=0, speed=1, center=False, train=False): + sampling_rate = self.target_sr + n_mels = self.n_mels + n_fft = self.n_fft + win_size = self.win_size + hop_length = self.hop_length + fmin = self.fmin + fmax = self.fmax + clip_val = self.clip_val + + factor = 2 ** (keyshift / 12) + n_fft_new = int(np.round(n_fft * factor)) + win_size_new = int(np.round(win_size * factor)) + hop_length_new = int(np.round(hop_length * speed)) + if not train: + mel_basis = self.mel_basis + hann_window = self.hann_window + else: + mel_basis = {} + hann_window = {} + + mel_basis_key = str(fmax) + "_" + str(y.device) + if mel_basis_key not in mel_basis: + mel = librosa_mel_fn( + sr=sampling_rate, n_fft=n_fft, n_mels=n_mels, fmin=fmin, fmax=fmax + ) + mel_basis[mel_basis_key] = torch.from_numpy(mel).float().to(y.device) + + keyshift_key = str(keyshift) + "_" + str(y.device) + if keyshift_key not in hann_window: + hann_window[keyshift_key] = torch.hann_window(win_size_new).to(y.device) + + pad_left = (win_size_new - hop_length_new) // 2 + pad_right = max( + (win_size_new - hop_length_new + 1) // 2, + win_size_new - y.size(-1) - pad_left, + ) + if pad_right < y.size(-1): + mode = "reflect" + else: + mode = "constant" + y = torch.nn.functional.pad(y.unsqueeze(1), (pad_left, pad_right), mode=mode) + y = y.squeeze(1) + + spec = torch.stft( + y, + n_fft_new, + hop_length=hop_length_new, + win_length=win_size_new, + window=hann_window[keyshift_key], + center=center, + pad_mode="reflect", + normalized=False, + onesided=True, + return_complex=True, + ) + spec = torch.sqrt(spec.real.pow(2) + spec.imag.pow(2) + (1e-9)) + if keyshift != 0: + size = n_fft // 2 + 1 + resize = spec.size(1) + if resize < size: + spec = F.pad(spec, (0, 0, 0, size - resize)) + spec = spec[:, :size, :] * win_size / win_size_new + spec = torch.matmul(mel_basis[mel_basis_key], spec) + spec = dynamic_range_compression_torch(spec, clip_val=clip_val) + return spec + + def __call__(self, audiopath): + audio, sr = load_wav_to_torch(audiopath, target_sr=self.target_sr) + spect = self.get_mel(audio.unsqueeze(0)).squeeze(0) + return spect + + +stft = STFT() + +# import fast_transformers.causal_product.causal_product_cuda + + +def softmax_kernel( + data, *, projection_matrix, is_query, normalize_data=True, eps=1e-4, device=None +): + b, h, *_ = data.shape + # (batch size, head, length, model_dim) + + # normalize model dim + data_normalizer = (data.shape[-1] ** -0.25) if normalize_data else 1.0 + + # what is ration?, projection_matrix.shape[0] --> 266 + + ratio = projection_matrix.shape[0] ** -0.5 + + projection = repeat(projection_matrix, "j d -> b h j d", b=b, h=h) + projection = projection.type_as(data) + + # data_dash = w^T x + data_dash = torch.einsum("...id,...jd->...ij", (data_normalizer * data), projection) + + # diag_data = D**2 + diag_data = data**2 + diag_data = torch.sum(diag_data, dim=-1) + diag_data = (diag_data / 2.0) * (data_normalizer**2) + diag_data = diag_data.unsqueeze(dim=-1) + + if is_query: + data_dash = ratio * ( + torch.exp( + data_dash + - diag_data + - torch.max(data_dash, dim=-1, keepdim=True).values + ) + + eps + ) + else: + data_dash = ratio * ( + torch.exp(data_dash - diag_data + eps) + ) # - torch.max(data_dash)) + eps) + + return data_dash.type_as(data) + + +def orthogonal_matrix_chunk(cols, qr_uniform_q=False, device=None): + unstructured_block = torch.randn((cols, cols), device=device) + q, r = torch.linalg.qr(unstructured_block.cpu(), mode="reduced") + q, r = map(lambda t: t.to(device), (q, r)) + + # proposed by @Parskatt + # to make sure Q is uniform https://arxiv.org/pdf/math-ph/0609050.pdf + if qr_uniform_q: + d = torch.diag(r, 0) + q *= d.sign() + return q.t() + + +def exists(val): + return val is not None + + +def empty(tensor): + return tensor.numel() == 0 + + +def default(val, d): + return val if exists(val) else d + + +def cast_tuple(val): + return (val,) if not isinstance(val, tuple) else val + + +class PCmer(nn.Module): + """The encoder that is used in the Transformer model.""" + + def __init__( + self, + num_layers, + num_heads, + dim_model, + dim_keys, + dim_values, + residual_dropout, + attention_dropout, + ): + super().__init__() + self.num_layers = num_layers + self.num_heads = num_heads + self.dim_model = dim_model + self.dim_values = dim_values + self.dim_keys = dim_keys + self.residual_dropout = residual_dropout + self.attention_dropout = attention_dropout + + self._layers = nn.ModuleList([_EncoderLayer(self) for _ in range(num_layers)]) + + # METHODS ######################################################################################################## + + def forward(self, phone, mask=None): + + # apply all layers to the input + for i, layer in enumerate(self._layers): + phone = layer(phone, mask) + # provide the final sequence + return phone + + +# ==================================================================================================================== # +# CLASS _ E N C O D E R L A Y E R # +# ==================================================================================================================== # + + +class _EncoderLayer(nn.Module): + """One layer of the encoder. + + Attributes: + attn: (:class:`mha.MultiHeadAttention`): The attention mechanism that is used to read the input sequence. + feed_forward (:class:`ffl.FeedForwardLayer`): The feed-forward layer on top of the attention mechanism. + """ + + def __init__(self, parent: PCmer): + """Creates a new instance of ``_EncoderLayer``. + + Args: + parent (Encoder): The encoder that the layers is created for. + """ + super().__init__() + + self.conformer = ConformerConvModule(parent.dim_model) + self.norm = nn.LayerNorm(parent.dim_model) + self.dropout = nn.Dropout(parent.residual_dropout) + + # selfatt -> fastatt: performer! + self.attn = SelfAttention( + dim=parent.dim_model, heads=parent.num_heads, causal=False + ) + + # METHODS ######################################################################################################## + + def forward(self, phone, mask=None): + + # compute attention sub-layer + phone = phone + (self.attn(self.norm(phone), mask=mask)) + + phone = phone + (self.conformer(phone)) + + return phone + + +def calc_same_padding(kernel_size): + pad = kernel_size // 2 + return (pad, pad - (kernel_size + 1) % 2) + + +# helper classes + + +class Swish(nn.Module): + def forward(self, x): + return x * x.sigmoid() + + +class Transpose(nn.Module): + def __init__(self, dims): + super().__init__() + assert len(dims) == 2, "dims must be a tuple of two dimensions" + self.dims = dims + + def forward(self, x): + return x.transpose(*self.dims) + + +class GLU(nn.Module): + def __init__(self, dim): + super().__init__() + self.dim = dim + + def forward(self, x): + out, gate = x.chunk(2, dim=self.dim) + return out * gate.sigmoid() + + +class DepthWiseConv1d(nn.Module): + def __init__(self, chan_in, chan_out, kernel_size, padding): + super().__init__() + self.padding = padding + self.conv = nn.Conv1d(chan_in, chan_out, kernel_size, groups=chan_in) + + def forward(self, x): + x = F.pad(x, self.padding) + return self.conv(x) + + +class ConformerConvModule(nn.Module): + def __init__( + self, dim, causal=False, expansion_factor=2, kernel_size=31, dropout=0.0 + ): + super().__init__() + + inner_dim = dim * expansion_factor + padding = calc_same_padding(kernel_size) if not causal else (kernel_size - 1, 0) + + self.net = nn.Sequential( + nn.LayerNorm(dim), + Transpose((1, 2)), + nn.Conv1d(dim, inner_dim * 2, 1), + GLU(dim=1), + DepthWiseConv1d( + inner_dim, inner_dim, kernel_size=kernel_size, padding=padding + ), + # nn.BatchNorm1d(inner_dim) if not causal else nn.Identity(), + Swish(), + nn.Conv1d(inner_dim, dim, 1), + Transpose((1, 2)), + nn.Dropout(dropout), + ) + + def forward(self, x): + return self.net(x) + + +def linear_attention(q, k, v): + if v is None: + out = torch.einsum("...ed,...nd->...ne", k, q) + return out + + else: + k_cumsum = k.sum(dim=-2) + # k_cumsum = k.sum(dim = -2) + D_inv = 1.0 / (torch.einsum("...nd,...d->...n", q, k_cumsum.type_as(q)) + 1e-8) + + context = torch.einsum("...nd,...ne->...de", k, v) + out = torch.einsum("...de,...nd,...n->...ne", context, q, D_inv) + return out + + +def gaussian_orthogonal_random_matrix( + nb_rows, nb_columns, scaling=0, qr_uniform_q=False, device=None +): + nb_full_blocks = int(nb_rows / nb_columns) + block_list = [] + + for _ in range(nb_full_blocks): + q = orthogonal_matrix_chunk( + nb_columns, qr_uniform_q=qr_uniform_q, device=device + ) + block_list.append(q) + + remaining_rows = nb_rows - nb_full_blocks * nb_columns + if remaining_rows > 0: + q = orthogonal_matrix_chunk( + nb_columns, qr_uniform_q=qr_uniform_q, device=device + ) + + block_list.append(q[:remaining_rows]) + + final_matrix = torch.cat(block_list) + + if scaling == 0: + multiplier = torch.randn((nb_rows, nb_columns), device=device).norm(dim=1) + elif scaling == 1: + multiplier = math.sqrt((float(nb_columns))) * torch.ones( + (nb_rows,), device=device + ) + else: + raise ValueError(f"Invalid scaling {scaling}") + + return torch.diag(multiplier) @ final_matrix + + +class FastAttention(nn.Module): + def __init__( + self, + dim_heads, + nb_features=None, + ortho_scaling=0, + causal=False, + generalized_attention=False, + kernel_fn=nn.ReLU(), + qr_uniform_q=False, + no_projection=False, + ): + super().__init__() + nb_features = default(nb_features, int(dim_heads * math.log(dim_heads))) + + self.dim_heads = dim_heads + self.nb_features = nb_features + self.ortho_scaling = ortho_scaling + + self.create_projection = partial( + gaussian_orthogonal_random_matrix, + nb_rows=self.nb_features, + nb_columns=dim_heads, + scaling=ortho_scaling, + qr_uniform_q=qr_uniform_q, + ) + projection_matrix = self.create_projection() + self.register_buffer("projection_matrix", projection_matrix) + + self.generalized_attention = generalized_attention + self.kernel_fn = kernel_fn + + # if this is turned on, no projection will be used + # queries and keys will be softmax-ed as in the original efficient attention paper + self.no_projection = no_projection + + self.causal = causal + + @torch.no_grad() + def redraw_projection_matrix(self): + projections = self.create_projection() + self.projection_matrix.copy_(projections) + del projections + + def forward(self, q, k, v): + device = q.device + + if self.no_projection: + q = q.softmax(dim=-1) + k = torch.exp(k) if self.causal else k.softmax(dim=-2) + else: + create_kernel = partial( + softmax_kernel, projection_matrix=self.projection_matrix, device=device + ) + + q = create_kernel(q, is_query=True) + k = create_kernel(k, is_query=False) + + attn_fn = linear_attention if not self.causal else self.causal_linear_fn + if v is None: + out = attn_fn(q, k, None) + return out + else: + out = attn_fn(q, k, v) + return out + + +class SelfAttention(nn.Module): + def __init__( + self, + dim, + causal=False, + heads=8, + dim_head=64, + local_heads=0, + local_window_size=256, + nb_features=None, + feature_redraw_interval=1000, + generalized_attention=False, + kernel_fn=nn.ReLU(), + qr_uniform_q=False, + dropout=0.0, + no_projection=False, + ): + super().__init__() + assert dim % heads == 0, "dimension must be divisible by number of heads" + dim_head = default(dim_head, dim // heads) + inner_dim = dim_head * heads + self.fast_attention = FastAttention( + dim_head, + nb_features, + causal=causal, + generalized_attention=generalized_attention, + kernel_fn=kernel_fn, + qr_uniform_q=qr_uniform_q, + no_projection=no_projection, + ) + + self.heads = heads + self.global_heads = heads - local_heads + self.local_attn = ( + LocalAttention( + window_size=local_window_size, + causal=causal, + autopad=True, + dropout=dropout, + look_forward=int(not causal), + rel_pos_emb_config=(dim_head, local_heads), + ) + if local_heads > 0 + else None + ) + + self.to_q = nn.Linear(dim, inner_dim) + self.to_k = nn.Linear(dim, inner_dim) + self.to_v = nn.Linear(dim, inner_dim) + self.to_out = nn.Linear(inner_dim, dim) + self.dropout = nn.Dropout(dropout) + + @torch.no_grad() + def redraw_projection_matrix(self): + self.fast_attention.redraw_projection_matrix() + + def forward( + self, + x, + context=None, + mask=None, + context_mask=None, + name=None, + inference=False, + **kwargs, + ): + _, _, _, h, gh = *x.shape, self.heads, self.global_heads + + cross_attend = exists(context) + + context = default(context, x) + context_mask = default(context_mask, mask) if not cross_attend else context_mask + q, k, v = self.to_q(x), self.to_k(context), self.to_v(context) + + q, k, v = map(lambda t: rearrange(t, "b n (h d) -> b h n d", h=h), (q, k, v)) + (q, lq), (k, lk), (v, lv) = map(lambda t: (t[:, :gh], t[:, gh:]), (q, k, v)) + + attn_outs = [] + if not empty(q): + if exists(context_mask): + global_mask = context_mask[:, None, :, None] + v.masked_fill_(~global_mask, 0.0) + if cross_attend: + pass + else: + out = self.fast_attention(q, k, v) + attn_outs.append(out) + + if not empty(lq): + assert ( + not cross_attend + ), "local attention is not compatible with cross attention" + out = self.local_attn(lq, lk, lv, input_mask=mask) + attn_outs.append(out) + + out = torch.cat(attn_outs, dim=1) + out = rearrange(out, "b h n d -> b n (h d)") + out = self.to_out(out) + return self.dropout(out) + + +def l2_regularization(model, l2_alpha): + l2_loss = [] + for module in model.modules(): + if type(module) is nn.Conv2d: + l2_loss.append((module.weight**2).sum() / 2.0) + return l2_alpha * sum(l2_loss) + + +class FCPE(nn.Module): + def __init__( + self, + input_channel=128, + out_dims=360, + n_layers=12, + n_chans=512, + use_siren=False, + use_full=False, + loss_mse_scale=10, + loss_l2_regularization=False, + loss_l2_regularization_scale=1, + loss_grad1_mse=False, + loss_grad1_mse_scale=1, + f0_max=1975.5, + f0_min=32.70, + confidence=False, + threshold=0.05, + use_input_conv=True, + ): + super().__init__() + if use_siren is True: + raise ValueError("Siren is not supported yet.") + if use_full is True: + raise ValueError("Full model is not supported yet.") + + self.loss_mse_scale = loss_mse_scale if (loss_mse_scale is not None) else 10 + self.loss_l2_regularization = ( + loss_l2_regularization if (loss_l2_regularization is not None) else False + ) + self.loss_l2_regularization_scale = ( + loss_l2_regularization_scale + if (loss_l2_regularization_scale is not None) + else 1 + ) + self.loss_grad1_mse = loss_grad1_mse if (loss_grad1_mse is not None) else False + self.loss_grad1_mse_scale = ( + loss_grad1_mse_scale if (loss_grad1_mse_scale is not None) else 1 + ) + self.f0_max = f0_max if (f0_max is not None) else 1975.5 + self.f0_min = f0_min if (f0_min is not None) else 32.70 + self.confidence = confidence if (confidence is not None) else False + self.threshold = threshold if (threshold is not None) else 0.05 + self.use_input_conv = use_input_conv if (use_input_conv is not None) else True + + self.cent_table_b = torch.Tensor( + np.linspace( + self.f0_to_cent(torch.Tensor([f0_min]))[0], + self.f0_to_cent(torch.Tensor([f0_max]))[0], + out_dims, + ) + ) + self.register_buffer("cent_table", self.cent_table_b) + + # conv in stack + _leaky = nn.LeakyReLU() + self.stack = nn.Sequential( + nn.Conv1d(input_channel, n_chans, 3, 1, 1), + nn.GroupNorm(4, n_chans), + _leaky, + nn.Conv1d(n_chans, n_chans, 3, 1, 1), + ) + + # transformer + self.decoder = PCmer( + num_layers=n_layers, + num_heads=8, + dim_model=n_chans, + dim_keys=n_chans, + dim_values=n_chans, + residual_dropout=0.1, + attention_dropout=0.1, + ) + self.norm = nn.LayerNorm(n_chans) + + # out + self.n_out = out_dims + self.dense_out = weight_norm(nn.Linear(n_chans, self.n_out)) + + def forward( + self, mel, infer=True, gt_f0=None, return_hz_f0=False, cdecoder="local_argmax" + ): + """ + input: + B x n_frames x n_unit + return: + dict of B x n_frames x feat + """ + if cdecoder == "argmax": + self.cdecoder = self.cents_decoder + elif cdecoder == "local_argmax": + self.cdecoder = self.cents_local_decoder + if self.use_input_conv: + x = self.stack(mel.transpose(1, 2)).transpose(1, 2) + else: + x = mel + x = self.decoder(x) + x = self.norm(x) + x = self.dense_out(x) # [B,N,D] + x = torch.sigmoid(x) + if not infer: + gt_cent_f0 = self.f0_to_cent(gt_f0) # mel f0 #[B,N,1] + gt_cent_f0 = self.gaussian_blurred_cent(gt_cent_f0) # #[B,N,out_dim] + loss_all = self.loss_mse_scale * F.binary_cross_entropy( + x, gt_cent_f0 + ) # bce loss + # l2 regularization + if self.loss_l2_regularization: + loss_all = loss_all + l2_regularization( + model=self, l2_alpha=self.loss_l2_regularization_scale + ) + x = loss_all + if infer: + x = self.cdecoder(x) + x = self.cent_to_f0(x) + if not return_hz_f0: + x = (1 + x / 700).log() + return x + + def cents_decoder(self, y, mask=True): + B, N, _ = y.size() + ci = self.cent_table[None, None, :].expand(B, N, -1) + rtn = torch.sum(ci * y, dim=-1, keepdim=True) / torch.sum( + y, dim=-1, keepdim=True + ) # cents: [B,N,1] + if mask: + confident = torch.max(y, dim=-1, keepdim=True)[0] + confident_mask = torch.ones_like(confident) + confident_mask[confident <= self.threshold] = float("-INF") + rtn = rtn * confident_mask + if self.confidence: + return rtn, confident + else: + return rtn + + def cents_local_decoder(self, y, mask=True): + B, N, _ = y.size() + ci = self.cent_table[None, None, :].expand(B, N, -1) + confident, max_index = torch.max(y, dim=-1, keepdim=True) + local_argmax_index = torch.arange(0, 9).to(max_index.device) + (max_index - 4) + local_argmax_index[local_argmax_index < 0] = 0 + local_argmax_index[local_argmax_index >= self.n_out] = self.n_out - 1 + ci_l = torch.gather(ci, -1, local_argmax_index) + y_l = torch.gather(y, -1, local_argmax_index) + rtn = torch.sum(ci_l * y_l, dim=-1, keepdim=True) / torch.sum( + y_l, dim=-1, keepdim=True + ) # cents: [B,N,1] + if mask: + confident_mask = torch.ones_like(confident) + confident_mask[confident <= self.threshold] = float("-INF") + rtn = rtn * confident_mask + if self.confidence: + return rtn, confident + else: + return rtn + + def cent_to_f0(self, cent): + return 10.0 * 2 ** (cent / 1200.0) + + def f0_to_cent(self, f0): + return 1200.0 * torch.log2(f0 / 10.0) + + def gaussian_blurred_cent(self, cents): # cents: [B,N,1] + mask = (cents > 0.1) & (cents < (1200.0 * np.log2(self.f0_max / 10.0))) + B, N, _ = cents.size() + ci = self.cent_table[None, None, :].expand(B, N, -1) + return torch.exp(-torch.square(ci - cents) / 1250) * mask.float() + + +class FCPEInfer: + def __init__(self, model_path, device=None, dtype=torch.float32): + if device is None: + device = "cuda" if torch.cuda.is_available() else "cpu" + self.device = device + ckpt = torch.load(model_path, map_location=torch.device(self.device)) + self.args = DotDict(ckpt["config"]) + self.dtype = dtype + model = FCPE( + input_channel=self.args.model.input_channel, + out_dims=self.args.model.out_dims, + n_layers=self.args.model.n_layers, + n_chans=self.args.model.n_chans, + use_siren=self.args.model.use_siren, + use_full=self.args.model.use_full, + loss_mse_scale=self.args.loss.loss_mse_scale, + loss_l2_regularization=self.args.loss.loss_l2_regularization, + loss_l2_regularization_scale=self.args.loss.loss_l2_regularization_scale, + loss_grad1_mse=self.args.loss.loss_grad1_mse, + loss_grad1_mse_scale=self.args.loss.loss_grad1_mse_scale, + f0_max=self.args.model.f0_max, + f0_min=self.args.model.f0_min, + confidence=self.args.model.confidence, + ) + model.to(self.device).to(self.dtype) + model.load_state_dict(ckpt["model"]) + model.eval() + self.model = model + self.wav2mel = Wav2Mel(self.args, dtype=self.dtype, device=self.device) + + @torch.no_grad() + def __call__(self, audio, sr, threshold=0.05): + self.model.threshold = threshold + audio = audio[None, :] + mel = self.wav2mel(audio=audio, sample_rate=sr).to(self.dtype) + f0 = self.model(mel=mel, infer=True, return_hz_f0=True) + return f0 + + +class Wav2Mel: + + def __init__(self, args, device=None, dtype=torch.float32): + # self.args = args + self.sampling_rate = args.mel.sampling_rate + self.hop_size = args.mel.hop_size + if device is None: + device = "cuda" if torch.cuda.is_available() else "cpu" + self.device = device + self.dtype = dtype + self.stft = STFT( + args.mel.sampling_rate, + args.mel.num_mels, + args.mel.n_fft, + args.mel.win_size, + args.mel.hop_size, + args.mel.fmin, + args.mel.fmax, + ) + self.resample_kernel = {} + + def extract_nvstft(self, audio, keyshift=0, train=False): + mel = self.stft.get_mel(audio, keyshift=keyshift, train=train).transpose( + 1, 2 + ) # B, n_frames, bins + return mel + + def extract_mel(self, audio, sample_rate, keyshift=0, train=False): + audio = audio.to(self.dtype).to(self.device) + # resample + if sample_rate == self.sampling_rate: + audio_res = audio + else: + key_str = str(sample_rate) + if key_str not in self.resample_kernel: + self.resample_kernel[key_str] = Resample( + sample_rate, self.sampling_rate, lowpass_filter_width=128 + ) + self.resample_kernel[key_str] = ( + self.resample_kernel[key_str].to(self.dtype).to(self.device) + ) + audio_res = self.resample_kernel[key_str](audio) + + # extract + mel = self.extract_nvstft( + audio_res, keyshift=keyshift, train=train + ) # B, n_frames, bins + n_frames = int(audio.shape[1] // self.hop_size) + 1 + if n_frames > int(mel.shape[1]): + mel = torch.cat((mel, mel[:, -1:, :]), 1) + if n_frames < int(mel.shape[1]): + mel = mel[:, :n_frames, :] + return mel + + def __call__(self, audio, sample_rate, keyshift=0, train=False): + return self.extract_mel(audio, sample_rate, keyshift=keyshift, train=train) + + +class DotDict(dict): + def __getattr__(*args): + val = dict.get(*args) + return DotDict(val) if type(val) is dict else val + + __setattr__ = dict.__setitem__ + __delattr__ = dict.__delitem__ + + +class F0Predictor(object): + def compute_f0(self, wav, p_len): + """ + input: wav:[signal_length] + p_len:int + output: f0:[signal_length//hop_length] + """ + pass + + def compute_f0_uv(self, wav, p_len): + """ + input: wav:[signal_length] + p_len:int + output: f0:[signal_length//hop_length],uv:[signal_length//hop_length] + """ + pass + + +class FCPEF0Predictor(F0Predictor): + def __init__( + self, + model_path, + hop_length=512, + f0_min=50, + f0_max=1100, + dtype=torch.float32, + device=None, + sampling_rate=44100, + threshold=0.05, + ): + self.fcpe = FCPEInfer(model_path, device=device, dtype=dtype) + self.hop_length = hop_length + self.f0_min = f0_min + self.f0_max = f0_max + if device is None: + self.device = "cuda" if torch.cuda.is_available() else "cpu" + else: + self.device = device + self.threshold = threshold + self.sampling_rate = sampling_rate + self.dtype = dtype + self.name = "fcpe" + + def repeat_expand( + self, + content: Union[torch.Tensor, np.ndarray], + target_len: int, + mode: str = "nearest", + ): + ndim = content.ndim + + if content.ndim == 1: + content = content[None, None] + elif content.ndim == 2: + content = content[None] + + assert content.ndim == 3 + + is_np = isinstance(content, np.ndarray) + if is_np: + content = torch.from_numpy(content) + + results = torch.nn.functional.interpolate(content, size=target_len, mode=mode) + + if is_np: + results = results.numpy() + + if ndim == 1: + return results[0, 0] + elif ndim == 2: + return results[0] + + def post_process(self, x, sampling_rate, f0, pad_to): + if isinstance(f0, np.ndarray): + f0 = torch.from_numpy(f0).float().to(x.device) + + if pad_to is None: + return f0 + + f0 = self.repeat_expand(f0, pad_to) + + vuv_vector = torch.zeros_like(f0) + vuv_vector[f0 > 0.0] = 1.0 + vuv_vector[f0 <= 0.0] = 0.0 + + # 去掉0频率, 并线性插值 + nzindex = torch.nonzero(f0).squeeze() + f0 = torch.index_select(f0, dim=0, index=nzindex).cpu().numpy() + time_org = self.hop_length / sampling_rate * nzindex.cpu().numpy() + time_frame = np.arange(pad_to) * self.hop_length / sampling_rate + + vuv_vector = F.interpolate(vuv_vector[None, None, :], size=pad_to)[0][0] + + if f0.shape[0] <= 0: + return ( + torch.zeros(pad_to, dtype=torch.float, device=x.device).cpu().numpy(), + vuv_vector.cpu().numpy(), + ) + if f0.shape[0] == 1: + return ( + torch.ones(pad_to, dtype=torch.float, device=x.device) * f0[0] + ).cpu().numpy(), vuv_vector.cpu().numpy() + + # 大概可以用 torch 重写? + f0 = np.interp(time_frame, time_org, f0, left=f0[0], right=f0[-1]) + # vuv_vector = np.ceil(scipy.ndimage.zoom(vuv_vector,pad_to/len(vuv_vector),order = 0)) + + return f0, vuv_vector.cpu().numpy() + + def compute_f0(self, wav, p_len=None): + x = torch.FloatTensor(wav).to(self.dtype).to(self.device) + if p_len is None: + print("fcpe p_len is None") + p_len = x.shape[0] // self.hop_length + f0 = self.fcpe(x, sr=self.sampling_rate, threshold=self.threshold)[0, :, 0] + if torch.all(f0 == 0): + rtn = f0.cpu().numpy() if p_len is None else np.zeros(p_len) + return rtn, rtn + return self.post_process(x, self.sampling_rate, f0, p_len)[0] + + def compute_f0_uv(self, wav, p_len=None): + x = torch.FloatTensor(wav).to(self.dtype).to(self.device) + if p_len is None: + p_len = x.shape[0] // self.hop_length + f0 = self.fcpe(x, sr=self.sampling_rate, threshold=self.threshold)[0, :, 0] + if torch.all(f0 == 0): + rtn = f0.cpu().numpy() if p_len is None else np.zeros(p_len) + return rtn, rtn + return self.post_process(x, self.sampling_rate, f0, p_len) diff --git a/system/tts_engines/rvc/lib/infer_pack/__init__.py b/system/tts_engines/rvc/lib/infer_pack/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/system/tts_engines/rvc/lib/infer_pack/attentions.py b/system/tts_engines/rvc/lib/infer_pack/attentions.py new file mode 100644 index 0000000..ea728e2 --- /dev/null +++ b/system/tts_engines/rvc/lib/infer_pack/attentions.py @@ -0,0 +1,398 @@ +import math +import torch +from torch import nn +from torch.nn import functional as F + +from . import commons +from .modules import LayerNorm + + +class Encoder(nn.Module): + def __init__( + self, + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size=1, + p_dropout=0.0, + window_size=10, + **kwargs + ): + super().__init__() + self.hidden_channels = hidden_channels + self.filter_channels = filter_channels + self.n_heads = n_heads + self.n_layers = n_layers + self.kernel_size = kernel_size + self.p_dropout = p_dropout + self.window_size = window_size + + self.drop = nn.Dropout(p_dropout) + self.attn_layers = nn.ModuleList() + self.norm_layers_1 = nn.ModuleList() + self.ffn_layers = nn.ModuleList() + self.norm_layers_2 = nn.ModuleList() + for i in range(self.n_layers): + self.attn_layers.append( + MultiHeadAttention( + hidden_channels, + hidden_channels, + n_heads, + p_dropout=p_dropout, + window_size=window_size, + ) + ) + self.norm_layers_1.append(LayerNorm(hidden_channels)) + self.ffn_layers.append( + FFN( + hidden_channels, + hidden_channels, + filter_channels, + kernel_size, + p_dropout=p_dropout, + ) + ) + self.norm_layers_2.append(LayerNorm(hidden_channels)) + + def forward(self, x, x_mask): + attn_mask = x_mask.unsqueeze(2) * x_mask.unsqueeze(-1) + x = x * x_mask + for i in range(self.n_layers): + y = self.attn_layers[i](x, x, attn_mask) + y = self.drop(y) + x = self.norm_layers_1[i](x + y) + + y = self.ffn_layers[i](x, x_mask) + y = self.drop(y) + x = self.norm_layers_2[i](x + y) + x = x * x_mask + return x + + +class Decoder(nn.Module): + def __init__( + self, + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size=1, + p_dropout=0.0, + proximal_bias=False, + proximal_init=True, + **kwargs + ): + super().__init__() + self.hidden_channels = hidden_channels + self.filter_channels = filter_channels + self.n_heads = n_heads + self.n_layers = n_layers + self.kernel_size = kernel_size + self.p_dropout = p_dropout + self.proximal_bias = proximal_bias + self.proximal_init = proximal_init + + self.drop = nn.Dropout(p_dropout) + self.self_attn_layers = nn.ModuleList() + self.norm_layers_0 = nn.ModuleList() + self.encdec_attn_layers = nn.ModuleList() + self.norm_layers_1 = nn.ModuleList() + self.ffn_layers = nn.ModuleList() + self.norm_layers_2 = nn.ModuleList() + for i in range(self.n_layers): + self.self_attn_layers.append( + MultiHeadAttention( + hidden_channels, + hidden_channels, + n_heads, + p_dropout=p_dropout, + proximal_bias=proximal_bias, + proximal_init=proximal_init, + ) + ) + self.norm_layers_0.append(LayerNorm(hidden_channels)) + self.encdec_attn_layers.append( + MultiHeadAttention( + hidden_channels, hidden_channels, n_heads, p_dropout=p_dropout + ) + ) + self.norm_layers_1.append(LayerNorm(hidden_channels)) + self.ffn_layers.append( + FFN( + hidden_channels, + hidden_channels, + filter_channels, + kernel_size, + p_dropout=p_dropout, + causal=True, + ) + ) + self.norm_layers_2.append(LayerNorm(hidden_channels)) + + def forward(self, x, x_mask, h, h_mask): + """ + x: decoder input + h: encoder output + """ + self_attn_mask = commons.subsequent_mask(x_mask.size(2)).to( + device=x.device, dtype=x.dtype + ) + encdec_attn_mask = h_mask.unsqueeze(2) * x_mask.unsqueeze(-1) + x = x * x_mask + for i in range(self.n_layers): + y = self.self_attn_layers[i](x, x, self_attn_mask) + y = self.drop(y) + x = self.norm_layers_0[i](x + y) + + y = self.encdec_attn_layers[i](x, h, encdec_attn_mask) + y = self.drop(y) + x = self.norm_layers_1[i](x + y) + + y = self.ffn_layers[i](x, x_mask) + y = self.drop(y) + x = self.norm_layers_2[i](x + y) + x = x * x_mask + return x + + +class MultiHeadAttention(nn.Module): + def __init__( + self, + channels, + out_channels, + n_heads, + p_dropout=0.0, + window_size=None, + heads_share=True, + block_length=None, + proximal_bias=False, + proximal_init=False, + ): + super().__init__() + assert channels % n_heads == 0 + + self.channels = channels + self.out_channels = out_channels + self.n_heads = n_heads + self.p_dropout = p_dropout + self.window_size = window_size + self.heads_share = heads_share + self.block_length = block_length + self.proximal_bias = proximal_bias + self.proximal_init = proximal_init + self.attn = None + + self.k_channels = channels // n_heads + self.conv_q = nn.Conv1d(channels, channels, 1) + self.conv_k = nn.Conv1d(channels, channels, 1) + self.conv_v = nn.Conv1d(channels, channels, 1) + self.conv_o = nn.Conv1d(channels, out_channels, 1) + self.drop = nn.Dropout(p_dropout) + + if window_size is not None: + n_heads_rel = 1 if heads_share else n_heads + rel_stddev = self.k_channels**-0.5 + self.emb_rel_k = nn.Parameter( + torch.randn(n_heads_rel, window_size * 2 + 1, self.k_channels) + * rel_stddev + ) + self.emb_rel_v = nn.Parameter( + torch.randn(n_heads_rel, window_size * 2 + 1, self.k_channels) + * rel_stddev + ) + + nn.init.xavier_uniform_(self.conv_q.weight) + nn.init.xavier_uniform_(self.conv_k.weight) + nn.init.xavier_uniform_(self.conv_v.weight) + if proximal_init: + with torch.no_grad(): + self.conv_k.weight.copy_(self.conv_q.weight) + self.conv_k.bias.copy_(self.conv_q.bias) + + def forward(self, x, c, attn_mask=None): + q = self.conv_q(x) + k = self.conv_k(c) + v = self.conv_v(c) + + x, self.attn = self.attention(q, k, v, mask=attn_mask) + + x = self.conv_o(x) + return x + + def attention(self, query, key, value, mask=None): + b, d, t_s, t_t = (*key.size(), query.size(2)) + query = query.view(b, self.n_heads, self.k_channels, t_t).transpose(2, 3) + key = key.view(b, self.n_heads, self.k_channels, t_s).transpose(2, 3) + value = value.view(b, self.n_heads, self.k_channels, t_s).transpose(2, 3) + + scores = torch.matmul(query / math.sqrt(self.k_channels), key.transpose(-2, -1)) + if self.window_size is not None: + assert ( + t_s == t_t + ), "Relative attention is only available for self-attention." + key_relative_embeddings = self._get_relative_embeddings(self.emb_rel_k, t_s) + rel_logits = self._matmul_with_relative_keys( + query / math.sqrt(self.k_channels), key_relative_embeddings + ) + scores_local = self._relative_position_to_absolute_position(rel_logits) + scores = scores + scores_local + if self.proximal_bias: + assert t_s == t_t, "Proximal bias is only available for self-attention." + scores = scores + self._attention_bias_proximal(t_s).to( + device=scores.device, dtype=scores.dtype + ) + if mask is not None: + scores = scores.masked_fill(mask == 0, -1e4) + if self.block_length is not None: + assert ( + t_s == t_t + ), "Local attention is only available for self-attention." + block_mask = ( + torch.ones_like(scores) + .triu(-self.block_length) + .tril(self.block_length) + ) + scores = scores.masked_fill(block_mask == 0, -1e4) + p_attn = F.softmax(scores, dim=-1) + p_attn = self.drop(p_attn) + output = torch.matmul(p_attn, value) + if self.window_size is not None: + relative_weights = self._absolute_position_to_relative_position(p_attn) + value_relative_embeddings = self._get_relative_embeddings( + self.emb_rel_v, t_s + ) + output = output + self._matmul_with_relative_values( + relative_weights, value_relative_embeddings + ) + output = output.transpose(2, 3).contiguous().view(b, d, t_t) + return output, p_attn + + def _matmul_with_relative_values(self, x, y): + """ + x: [b, h, l, m] + y: [h or 1, m, d] + ret: [b, h, l, d] + """ + ret = torch.matmul(x, y.unsqueeze(0)) + return ret + + def _matmul_with_relative_keys(self, x, y): + """ + x: [b, h, l, d] + y: [h or 1, m, d] + ret: [b, h, l, m] + """ + ret = torch.matmul(x, y.unsqueeze(0).transpose(-2, -1)) + return ret + + def _get_relative_embeddings(self, relative_embeddings, length): + pad_length = max(length - (self.window_size + 1), 0) + slice_start_position = max((self.window_size + 1) - length, 0) + slice_end_position = slice_start_position + 2 * length - 1 + if pad_length > 0: + padded_relative_embeddings = F.pad( + relative_embeddings, + commons.convert_pad_shape([[0, 0], [pad_length, pad_length], [0, 0]]), + ) + else: + padded_relative_embeddings = relative_embeddings + used_relative_embeddings = padded_relative_embeddings[ + :, slice_start_position:slice_end_position + ] + return used_relative_embeddings + + def _relative_position_to_absolute_position(self, x): + """ + x: [b, h, l, 2*l-1] + ret: [b, h, l, l] + """ + batch, heads, length, _ = x.size() + + x = F.pad(x, commons.convert_pad_shape([[0, 0], [0, 0], [0, 0], [0, 1]])) + x_flat = x.view([batch, heads, length * 2 * length]) + x_flat = F.pad( + x_flat, commons.convert_pad_shape([[0, 0], [0, 0], [0, length - 1]]) + ) + + x_final = x_flat.view([batch, heads, length + 1, 2 * length - 1])[ + :, :, :length, length - 1 : + ] + return x_final + + def _absolute_position_to_relative_position(self, x): + """ + x: [b, h, l, l] + ret: [b, h, l, 2*l-1] + """ + batch, heads, length, _ = x.size() + x = F.pad( + x, commons.convert_pad_shape([[0, 0], [0, 0], [0, 0], [0, length - 1]]) + ) + x_flat = x.view([batch, heads, length**2 + length * (length - 1)]) + x_flat = F.pad(x_flat, commons.convert_pad_shape([[0, 0], [0, 0], [length, 0]])) + x_final = x_flat.view([batch, heads, length, 2 * length])[:, :, :, 1:] + return x_final + + def _attention_bias_proximal(self, length): + r = torch.arange(length, dtype=torch.float32) + diff = torch.unsqueeze(r, 0) - torch.unsqueeze(r, 1) + return torch.unsqueeze(torch.unsqueeze(-torch.log1p(torch.abs(diff)), 0), 0) + + +class FFN(nn.Module): + def __init__( + self, + in_channels, + out_channels, + filter_channels, + kernel_size, + p_dropout=0.0, + activation=None, + causal=False, + ): + super().__init__() + self.in_channels = in_channels + self.out_channels = out_channels + self.filter_channels = filter_channels + self.kernel_size = kernel_size + self.p_dropout = p_dropout + self.activation = activation + self.causal = causal + + if causal: + self.padding = self._causal_padding + else: + self.padding = self._same_padding + + self.conv_1 = nn.Conv1d(in_channels, filter_channels, kernel_size) + self.conv_2 = nn.Conv1d(filter_channels, out_channels, kernel_size) + self.drop = nn.Dropout(p_dropout) + + def forward(self, x, x_mask): + x = self.conv_1(self.padding(x * x_mask)) + if self.activation == "gelu": + x = x * torch.sigmoid(1.702 * x) + else: + x = torch.relu(x) + x = self.drop(x) + x = self.conv_2(self.padding(x * x_mask)) + return x * x_mask + + def _causal_padding(self, x): + if self.kernel_size == 1: + return x + pad_l = self.kernel_size - 1 + pad_r = 0 + padding = [[0, 0], [0, 0], [pad_l, pad_r]] + x = F.pad(x, commons.convert_pad_shape(padding)) + return x + + def _same_padding(self, x): + if self.kernel_size == 1: + return x + pad_l = (self.kernel_size - 1) // 2 + pad_r = self.kernel_size // 2 + padding = [[0, 0], [0, 0], [pad_l, pad_r]] + x = F.pad(x, commons.convert_pad_shape(padding)) + return x diff --git a/system/tts_engines/rvc/lib/infer_pack/commons.py b/system/tts_engines/rvc/lib/infer_pack/commons.py new file mode 100644 index 0000000..5447098 --- /dev/null +++ b/system/tts_engines/rvc/lib/infer_pack/commons.py @@ -0,0 +1,166 @@ +import math +import numpy as np +import torch +from torch import nn +from torch.nn import functional as F + + +def init_weights(m, mean=0.0, std=0.01): + classname = m.__class__.__name__ + if classname.find("Conv") != -1: + m.weight.data.normal_(mean, std) + + +def get_padding(kernel_size, dilation=1): + return int((kernel_size * dilation - dilation) / 2) + + +def convert_pad_shape(pad_shape): + l = pad_shape[::-1] + pad_shape = [item for sublist in l for item in sublist] + return pad_shape + + +def kl_divergence(m_p, logs_p, m_q, logs_q): + """KL(P||Q)""" + kl = (logs_q - logs_p) - 0.5 + kl += ( + 0.5 * (torch.exp(2.0 * logs_p) + ((m_p - m_q) ** 2)) * torch.exp(-2.0 * logs_q) + ) + return kl + + +def rand_gumbel(shape): + """Sample from the Gumbel distribution, protect from overflows.""" + uniform_samples = torch.rand(shape) * 0.99998 + 0.00001 + return -torch.log(-torch.log(uniform_samples)) + + +def rand_gumbel_like(x): + g = rand_gumbel(x.size()).to(dtype=x.dtype, device=x.device) + return g + + +def slice_segments(x, ids_str, segment_size=4): + ret = torch.zeros_like(x[:, :, :segment_size]) + for i in range(x.size(0)): + idx_str = ids_str[i] + idx_end = idx_str + segment_size + ret[i] = x[i, :, idx_str:idx_end] + return ret + + +def slice_segments2(x, ids_str, segment_size=4): + ret = torch.zeros_like(x[:, :segment_size]) + for i in range(x.size(0)): + idx_str = ids_str[i] + idx_end = idx_str + segment_size + ret[i] = x[i, idx_str:idx_end] + return ret + + +def rand_slice_segments(x, x_lengths=None, segment_size=4): + b, d, t = x.size() + if x_lengths is None: + x_lengths = t + ids_str_max = x_lengths - segment_size + 1 + ids_str = (torch.rand([b]).to(device=x.device) * ids_str_max).to(dtype=torch.long) + ret = slice_segments(x, ids_str, segment_size) + return ret, ids_str + + +def get_timing_signal_1d(length, channels, min_timescale=1.0, max_timescale=1.0e4): + position = torch.arange(length, dtype=torch.float) + num_timescales = channels // 2 + log_timescale_increment = math.log(float(max_timescale) / float(min_timescale)) / ( + num_timescales - 1 + ) + inv_timescales = min_timescale * torch.exp( + torch.arange(num_timescales, dtype=torch.float) * -log_timescale_increment + ) + scaled_time = position.unsqueeze(0) * inv_timescales.unsqueeze(1) + signal = torch.cat([torch.sin(scaled_time), torch.cos(scaled_time)], 0) + signal = F.pad(signal, [0, 0, 0, channels % 2]) + signal = signal.view(1, channels, length) + return signal + + +def add_timing_signal_1d(x, min_timescale=1.0, max_timescale=1.0e4): + b, channels, length = x.size() + signal = get_timing_signal_1d(length, channels, min_timescale, max_timescale) + return x + signal.to(dtype=x.dtype, device=x.device) + + +def cat_timing_signal_1d(x, min_timescale=1.0, max_timescale=1.0e4, axis=1): + b, channels, length = x.size() + signal = get_timing_signal_1d(length, channels, min_timescale, max_timescale) + return torch.cat([x, signal.to(dtype=x.dtype, device=x.device)], axis) + + +def subsequent_mask(length): + mask = torch.tril(torch.ones(length, length)).unsqueeze(0).unsqueeze(0) + return mask + + +@torch.jit.script +def fused_add_tanh_sigmoid_multiply(input_a, input_b, n_channels): + n_channels_int = n_channels[0] + in_act = input_a + input_b + t_act = torch.tanh(in_act[:, :n_channels_int, :]) + s_act = torch.sigmoid(in_act[:, n_channels_int:, :]) + acts = t_act * s_act + return acts + + +def convert_pad_shape(pad_shape): + l = pad_shape[::-1] + pad_shape = [item for sublist in l for item in sublist] + return pad_shape + + +def shift_1d(x): + x = F.pad(x, convert_pad_shape([[0, 0], [0, 0], [1, 0]]))[:, :, :-1] + return x + + +def sequence_mask(length, max_length=None): + if max_length is None: + max_length = length.max() + x = torch.arange(max_length, dtype=length.dtype, device=length.device) + return x.unsqueeze(0) < length.unsqueeze(1) + + +def generate_path(duration, mask): + """ + duration: [b, 1, t_x] + mask: [b, 1, t_y, t_x] + """ + device = duration.device + + b, _, t_y, t_x = mask.shape + cum_duration = torch.cumsum(duration, -1) + + cum_duration_flat = cum_duration.view(b * t_x) + path = sequence_mask(cum_duration_flat, t_y).to(mask.dtype) + path = path.view(b, t_x, t_y) + path = path - F.pad(path, convert_pad_shape([[0, 0], [1, 0], [0, 0]]))[:, :-1] + path = path.unsqueeze(1).transpose(2, 3) * mask + return path + + +def clip_grad_value_(parameters, clip_value, norm_type=2): + if isinstance(parameters, torch.Tensor): + parameters = [parameters] + parameters = list(filter(lambda p: p.grad is not None, parameters)) + norm_type = float(norm_type) + if clip_value is not None: + clip_value = float(clip_value) + + total_norm = 0 + for p in parameters: + param_norm = p.grad.data.norm(norm_type) + total_norm += param_norm.item() ** norm_type + if clip_value is not None: + p.grad.data.clamp_(min=-clip_value, max=clip_value) + total_norm = total_norm ** (1.0 / norm_type) + return total_norm diff --git a/system/tts_engines/rvc/lib/infer_pack/models.py b/system/tts_engines/rvc/lib/infer_pack/models.py new file mode 100644 index 0000000..dcd0a57 --- /dev/null +++ b/system/tts_engines/rvc/lib/infer_pack/models.py @@ -0,0 +1,1382 @@ +import math +import torch +from torch import nn +from torch.nn import functional as F +from . import modules +from . import attentions +from . import commons +from .commons import init_weights, get_padding +from torch.nn import Conv1d, ConvTranspose1d, Conv2d +from torch.nn.utils import remove_weight_norm +from torch.nn.utils.parametrizations import spectral_norm, weight_norm +from typing import Optional + +has_xpu = bool(hasattr(torch, "xpu") and torch.xpu.is_available()) + + +class TextEncoder256(nn.Module): + def __init__( + self, + out_channels, + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size, + p_dropout, + f0=True, + ): + super(TextEncoder256, self).__init__() + self.out_channels = out_channels + self.hidden_channels = hidden_channels + self.filter_channels = filter_channels + self.n_heads = n_heads + self.n_layers = n_layers + self.kernel_size = kernel_size + self.p_dropout = float(p_dropout) + self.emb_phone = nn.Linear(256, hidden_channels) + self.lrelu = nn.LeakyReLU(0.1, inplace=True) + if f0 == True: + self.emb_pitch = nn.Embedding(256, hidden_channels) # pitch 256 + self.encoder = attentions.Encoder( + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size, + float(p_dropout), + ) + self.proj = nn.Conv1d(hidden_channels, out_channels * 2, 1) + + def forward( + self, phone: torch.Tensor, pitch: Optional[torch.Tensor], lengths: torch.Tensor + ): + if pitch is None: + x = self.emb_phone(phone) + else: + x = self.emb_phone(phone) + self.emb_pitch(pitch) + x = x * math.sqrt(self.hidden_channels) # [b, t, h] + x = self.lrelu(x) + x = torch.transpose(x, 1, -1) # [b, h, t] + x_mask = torch.unsqueeze(commons.sequence_mask(lengths, x.size(2)), 1).to( + x.dtype + ) + x = self.encoder(x * x_mask, x_mask) + stats = self.proj(x) * x_mask + + m, logs = torch.split(stats, self.out_channels, dim=1) + return m, logs, x_mask + + +class TextEncoder768(nn.Module): + def __init__( + self, + out_channels, + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size, + p_dropout, + f0=True, + ): + super(TextEncoder768, self).__init__() + self.out_channels = out_channels + self.hidden_channels = hidden_channels + self.filter_channels = filter_channels + self.n_heads = n_heads + self.n_layers = n_layers + self.kernel_size = kernel_size + self.p_dropout = float(p_dropout) + self.emb_phone = nn.Linear(768, hidden_channels) + self.lrelu = nn.LeakyReLU(0.1, inplace=True) + if f0 == True: + self.emb_pitch = nn.Embedding(256, hidden_channels) # pitch 256 + self.encoder = attentions.Encoder( + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size, + float(p_dropout), + ) + self.proj = nn.Conv1d(hidden_channels, out_channels * 2, 1) + + def forward(self, phone: torch.Tensor, pitch: torch.Tensor, lengths: torch.Tensor): + if pitch is None: + x = self.emb_phone(phone) + else: + x = self.emb_phone(phone) + self.emb_pitch(pitch) + x = x * math.sqrt(self.hidden_channels) # [b, t, h] + x = self.lrelu(x) + x = torch.transpose(x, 1, -1) # [b, h, t] + x_mask = torch.unsqueeze(commons.sequence_mask(lengths, x.size(2)), 1).to( + x.dtype + ) + x = self.encoder(x * x_mask, x_mask) + stats = self.proj(x) * x_mask + + m, logs = torch.split(stats, self.out_channels, dim=1) + return m, logs, x_mask + + +class ResidualCouplingBlock(nn.Module): + def __init__( + self, + channels, + hidden_channels, + kernel_size, + dilation_rate, + n_layers, + n_flows=4, + gin_channels=0, + ): + super(ResidualCouplingBlock, self).__init__() + self.channels = channels + self.hidden_channels = hidden_channels + self.kernel_size = kernel_size + self.dilation_rate = dilation_rate + self.n_layers = n_layers + self.n_flows = n_flows + self.gin_channels = gin_channels + + self.flows = nn.ModuleList() + for i in range(n_flows): + self.flows.append( + modules.ResidualCouplingLayer( + channels, + hidden_channels, + kernel_size, + dilation_rate, + n_layers, + gin_channels=gin_channels, + mean_only=True, + ) + ) + self.flows.append(modules.Flip()) + + def forward( + self, + x: torch.Tensor, + x_mask: torch.Tensor, + g: Optional[torch.Tensor] = None, + reverse: bool = False, + ): + if not reverse: + for flow in self.flows: + x, _ = flow(x, x_mask, g=g, reverse=reverse) + else: + for flow in self.flows[::-1]: + x = flow.forward(x, x_mask, g=g, reverse=reverse) + return x + + def remove_weight_norm(self): + for i in range(self.n_flows): + self.flows[i * 2].remove_weight_norm() + + def __prepare_scriptable__(self): + for i in range(self.n_flows): + for hook in self.flows[i * 2]._forward_pre_hooks.values(): + if ( + hook.__module__ == "torch.nn.utils.parametrizations.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(self.flows[i * 2]) + + return self + + +class PosteriorEncoder(nn.Module): + def __init__( + self, + in_channels, + out_channels, + hidden_channels, + kernel_size, + dilation_rate, + n_layers, + gin_channels=0, + ): + super(PosteriorEncoder, self).__init__() + self.in_channels = in_channels + self.out_channels = out_channels + self.hidden_channels = hidden_channels + self.kernel_size = kernel_size + self.dilation_rate = dilation_rate + self.n_layers = n_layers + self.gin_channels = gin_channels + + self.pre = nn.Conv1d(in_channels, hidden_channels, 1) + self.enc = modules.WN( + hidden_channels, + kernel_size, + dilation_rate, + n_layers, + gin_channels=gin_channels, + ) + self.proj = nn.Conv1d(hidden_channels, out_channels * 2, 1) + + def forward( + self, x: torch.Tensor, x_lengths: torch.Tensor, g: Optional[torch.Tensor] = None + ): + x_mask = torch.unsqueeze(commons.sequence_mask(x_lengths, x.size(2)), 1).to( + x.dtype + ) + x = self.pre(x) * x_mask + x = self.enc(x, x_mask, g=g) + stats = self.proj(x) * x_mask + m, logs = torch.split(stats, self.out_channels, dim=1) + z = (m + torch.randn_like(m) * torch.exp(logs)) * x_mask + return z, m, logs, x_mask + + def remove_weight_norm(self): + self.enc.remove_weight_norm() + + def __prepare_scriptable__(self): + for hook in self.enc._forward_pre_hooks.values(): + if ( + hook.__module__ == "torch.nn.utils.parametrizations.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(self.enc) + return self + + +class Generator(torch.nn.Module): + def __init__( + self, + initial_channel, + resblock, + resblock_kernel_sizes, + resblock_dilation_sizes, + upsample_rates, + upsample_initial_channel, + upsample_kernel_sizes, + gin_channels=0, + ): + super(Generator, self).__init__() + self.num_kernels = len(resblock_kernel_sizes) + self.num_upsamples = len(upsample_rates) + self.conv_pre = Conv1d( + initial_channel, upsample_initial_channel, 7, 1, padding=3 + ) + resblock = modules.ResBlock1 if resblock == "1" else modules.ResBlock2 + + self.ups = nn.ModuleList() + for i, (u, k) in enumerate(zip(upsample_rates, upsample_kernel_sizes)): + self.ups.append( + weight_norm( + ConvTranspose1d( + upsample_initial_channel // (2**i), + upsample_initial_channel // (2 ** (i + 1)), + k, + u, + padding=(k - u) // 2, + ) + ) + ) + + self.resblocks = nn.ModuleList() + for i in range(len(self.ups)): + ch = upsample_initial_channel // (2 ** (i + 1)) + for j, (k, d) in enumerate( + zip(resblock_kernel_sizes, resblock_dilation_sizes) + ): + self.resblocks.append(resblock(ch, k, d)) + + self.conv_post = Conv1d(ch, 1, 7, 1, padding=3, bias=False) + self.ups.apply(init_weights) + + if gin_channels != 0: + self.cond = nn.Conv1d(gin_channels, upsample_initial_channel, 1) + + def forward(self, x: torch.Tensor, g: Optional[torch.Tensor] = None): + x = self.conv_pre(x) + if g is not None: + x = x + self.cond(g) + + for i in range(self.num_upsamples): + x = F.leaky_relu(x, modules.LRELU_SLOPE) + x = self.ups[i](x) + xs = None + for j in range(self.num_kernels): + if xs is None: + xs = self.resblocks[i * self.num_kernels + j](x) + else: + xs += self.resblocks[i * self.num_kernels + j](x) + x = xs / self.num_kernels + x = F.leaky_relu(x) + x = self.conv_post(x) + x = torch.tanh(x) + + return x + + def __prepare_scriptable__(self): + for l in self.ups: + for hook in l._forward_pre_hooks.values(): + # The hook we want to remove is an instance of WeightNorm class, so + # normally we would do `if isinstance(...)` but this class is not accessible + # because of shadowing, so we check the module name directly. + # https://github.com/pytorch/pytorch/blob/be0ca00c5ce260eb5bcec3237357f7a30cc08983/torch/nn/utils/__init__.py#L3 + if ( + hook.__module__ == "torch.nn.utils.parametrizations.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(l) + + for l in self.resblocks: + for hook in l._forward_pre_hooks.values(): + if ( + hook.__module__ == "torch.nn.utils.parametrizations.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(l) + return self + + def remove_weight_norm(self): + for l in self.ups: + remove_weight_norm(l) + for l in self.resblocks: + l.remove_weight_norm() + + +class SineGen(torch.nn.Module): + """Definition of sine generator + SineGen(samp_rate, harmonic_num = 0, + sine_amp = 0.1, noise_std = 0.003, + voiced_threshold = 0, + flag_for_pulse=False) + samp_rate: sampling rate in Hz + harmonic_num: number of harmonic overtones (default 0) + sine_amp: amplitude of sine-wavefrom (default 0.1) + noise_std: std of Gaussian noise (default 0.003) + voiced_thoreshold: F0 threshold for U/V classification (default 0) + flag_for_pulse: this SinGen is used inside PulseGen (default False) + Note: when flag_for_pulse is True, the first time step of a voiced + segment is always sin(torch.pi) or cos(0) + """ + + def __init__( + self, + samp_rate, + harmonic_num=0, + sine_amp=0.1, + noise_std=0.003, + voiced_threshold=0, + flag_for_pulse=False, + ): + super(SineGen, self).__init__() + self.sine_amp = sine_amp + self.noise_std = noise_std + self.harmonic_num = harmonic_num + self.dim = self.harmonic_num + 1 + self.sampling_rate = samp_rate + self.voiced_threshold = voiced_threshold + + def _f02uv(self, f0): + # generate uv signal + uv = torch.ones_like(f0) + uv = uv * (f0 > self.voiced_threshold) + if uv.device.type == "privateuseone": # for DirectML + uv = uv.float() + return uv + + def forward(self, f0: torch.Tensor, upp: int): + """sine_tensor, uv = forward(f0) + input F0: tensor(batchsize=1, length, dim=1) + f0 for unvoiced steps should be 0 + output sine_tensor: tensor(batchsize=1, length, dim) + output uv: tensor(batchsize=1, length, 1) + """ + with torch.no_grad(): + f0 = f0[:, None].transpose(1, 2) + f0_buf = torch.zeros(f0.shape[0], f0.shape[1], self.dim, device=f0.device) + # fundamental component + f0_buf[:, :, 0] = f0[:, :, 0] + for idx in range(self.harmonic_num): + f0_buf[:, :, idx + 1] = f0_buf[:, :, 0] * ( + idx + 2 + ) # idx + 2: the (idx+1)-th overtone, (idx+2)-th harmonic + rad_values = (f0_buf / float(self.sampling_rate)) % 1 + rand_ini = torch.rand( + f0_buf.shape[0], f0_buf.shape[2], device=f0_buf.device + ) + rand_ini[:, 0] = 0 + rad_values[:, 0, :] = rad_values[:, 0, :] + rand_ini + tmp_over_one = torch.cumsum(rad_values, 1) + tmp_over_one *= upp + tmp_over_one = F.interpolate( + tmp_over_one.transpose(2, 1), + scale_factor=float(upp), + mode="linear", + align_corners=True, + ).transpose(2, 1) + rad_values = F.interpolate( + rad_values.transpose(2, 1), scale_factor=float(upp), mode="nearest" + ).transpose( + 2, 1 + ) ####### + tmp_over_one %= 1 + tmp_over_one_idx = (tmp_over_one[:, 1:, :] - tmp_over_one[:, :-1, :]) < 0 + cumsum_shift = torch.zeros_like(rad_values) + cumsum_shift[:, 1:, :] = tmp_over_one_idx * -1.0 + sine_waves = torch.sin( + torch.cumsum(rad_values + cumsum_shift, dim=1) * 2 * torch.pi + ) + sine_waves = sine_waves * self.sine_amp + uv = self._f02uv(f0) + uv = F.interpolate( + uv.transpose(2, 1), scale_factor=float(upp), mode="nearest" + ).transpose(2, 1) + noise_amp = uv * self.noise_std + (1 - uv) * self.sine_amp / 3 + noise = noise_amp * torch.randn_like(sine_waves) + sine_waves = sine_waves * uv + noise + return sine_waves, uv, noise + + +class SourceModuleHnNSF(torch.nn.Module): + """SourceModule for hn-nsf + SourceModule(sampling_rate, harmonic_num=0, sine_amp=0.1, + add_noise_std=0.003, voiced_threshod=0) + sampling_rate: sampling_rate in Hz + harmonic_num: number of harmonic above F0 (default: 0) + sine_amp: amplitude of sine source signal (default: 0.1) + add_noise_std: std of additive Gaussian noise (default: 0.003) + note that amplitude of noise in unvoiced is decided + by sine_amp + voiced_threshold: threhold to set U/V given F0 (default: 0) + Sine_source, noise_source = SourceModuleHnNSF(F0_sampled) + F0_sampled (batchsize, length, 1) + Sine_source (batchsize, length, 1) + noise_source (batchsize, length 1) + uv (batchsize, length, 1) + """ + + def __init__( + self, + sampling_rate, + harmonic_num=0, + sine_amp=0.1, + add_noise_std=0.003, + voiced_threshod=0, + is_half=True, + ): + super(SourceModuleHnNSF, self).__init__() + + self.sine_amp = sine_amp + self.noise_std = add_noise_std + self.is_half = is_half + # to produce sine waveforms + self.l_sin_gen = SineGen( + sampling_rate, harmonic_num, sine_amp, add_noise_std, voiced_threshod + ) + + # to merge source harmonics into a single excitation + self.l_linear = torch.nn.Linear(harmonic_num + 1, 1) + self.l_tanh = torch.nn.Tanh() + # self.ddtype:int = -1 + + def forward(self, x: torch.Tensor, upp: int = 1): + # if self.ddtype ==-1: + # self.ddtype = self.l_linear.weight.dtype + sine_wavs, uv, _ = self.l_sin_gen(x, upp) + # print(x.dtype,sine_wavs.dtype,self.l_linear.weight.dtype) + # if self.is_half: + # sine_wavs = sine_wavs.half() + # sine_merge = self.l_tanh(self.l_linear(sine_wavs.to(x))) + # print(sine_wavs.dtype,self.ddtype) + # if sine_wavs.dtype != self.l_linear.weight.dtype: + sine_wavs = sine_wavs.to(dtype=self.l_linear.weight.dtype) + sine_merge = self.l_tanh(self.l_linear(sine_wavs)) + return sine_merge, None, None # noise, uv + + +class GeneratorNSF(torch.nn.Module): + def __init__( + self, + initial_channel, + resblock, + resblock_kernel_sizes, + resblock_dilation_sizes, + upsample_rates, + upsample_initial_channel, + upsample_kernel_sizes, + gin_channels, + sr, + is_half=False, + ): + super(GeneratorNSF, self).__init__() + self.num_kernels = len(resblock_kernel_sizes) + self.num_upsamples = len(upsample_rates) + + self.f0_upsamp = torch.nn.Upsample(scale_factor=math.prod(upsample_rates)) + self.m_source = SourceModuleHnNSF( + sampling_rate=sr, harmonic_num=0, is_half=is_half + ) + self.noise_convs = nn.ModuleList() + self.conv_pre = Conv1d( + initial_channel, upsample_initial_channel, 7, 1, padding=3 + ) + resblock = modules.ResBlock1 if resblock == "1" else modules.ResBlock2 + + self.ups = nn.ModuleList() + for i, (u, k) in enumerate(zip(upsample_rates, upsample_kernel_sizes)): + c_cur = upsample_initial_channel // (2 ** (i + 1)) + self.ups.append( + weight_norm( + ConvTranspose1d( + upsample_initial_channel // (2**i), + upsample_initial_channel // (2 ** (i + 1)), + k, + u, + padding=(k - u) // 2, + ) + ) + ) + if i + 1 < len(upsample_rates): + stride_f0 = math.prod(upsample_rates[i + 1 :]) + self.noise_convs.append( + Conv1d( + 1, + c_cur, + kernel_size=stride_f0 * 2, + stride=stride_f0, + padding=stride_f0 // 2, + ) + ) + else: + self.noise_convs.append(Conv1d(1, c_cur, kernel_size=1)) + + self.resblocks = nn.ModuleList() + for i in range(len(self.ups)): + ch = upsample_initial_channel // (2 ** (i + 1)) + for j, (k, d) in enumerate( + zip(resblock_kernel_sizes, resblock_dilation_sizes) + ): + self.resblocks.append(resblock(ch, k, d)) + + self.conv_post = Conv1d(ch, 1, 7, 1, padding=3, bias=False) + self.ups.apply(init_weights) + + if gin_channels != 0: + self.cond = nn.Conv1d(gin_channels, upsample_initial_channel, 1) + + self.upp = math.prod(upsample_rates) + + self.lrelu_slope = modules.LRELU_SLOPE + + def forward(self, x, f0, g: Optional[torch.Tensor] = None): + har_source, noi_source, uv = self.m_source(f0, self.upp) + har_source = har_source.transpose(1, 2) + x = self.conv_pre(x) + if g is not None: + x = x + self.cond(g) + # torch.jit.script() does not support direct indexing of torch modules + # That's why I wrote this + for i, (ups, noise_convs) in enumerate(zip(self.ups, self.noise_convs)): + if i < self.num_upsamples: + x = F.leaky_relu(x, self.lrelu_slope) + x = ups(x) + x_source = noise_convs(har_source) + x = x + x_source + xs: Optional[torch.Tensor] = None + l = [i * self.num_kernels + j for j in range(self.num_kernels)] + for j, resblock in enumerate(self.resblocks): + if j in l: + if xs is None: + xs = resblock(x) + else: + xs += resblock(x) + # This assertion cannot be ignored! \ + # If ignored, it will cause torch.jit.script() compilation errors + assert isinstance(xs, torch.Tensor) + x = xs / self.num_kernels + x = F.leaky_relu(x) + x = self.conv_post(x) + x = torch.tanh(x) + return x + + def remove_weight_norm(self): + for l in self.ups: + remove_weight_norm(l) + for l in self.resblocks: + l.remove_weight_norm() + + def __prepare_scriptable__(self): + for l in self.ups: + for hook in l._forward_pre_hooks.values(): + # The hook we want to remove is an instance of WeightNorm class, so + # normally we would do `if isinstance(...)` but this class is not accessible + # because of shadowing, so we check the module name directly. + # https://github.com/pytorch/pytorch/blob/be0ca00c5ce260eb5bcec3237357f7a30cc08983/torch/nn/utils/__init__.py#L3 + if ( + hook.__module__ == "torch.nn.utils.parametrizations.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(l) + for l in self.resblocks: + for hook in self.resblocks._forward_pre_hooks.values(): + if ( + hook.__module__ == "torch.nn.utils.parametrizations.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(l) + return self + + +class SynthesizerTrnMs256NSFsid(nn.Module): + def __init__( + self, + spec_channels, + segment_size, + inter_channels, + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size, + p_dropout, + resblock, + resblock_kernel_sizes, + resblock_dilation_sizes, + upsample_rates, + upsample_initial_channel, + upsample_kernel_sizes, + spk_embed_dim, + gin_channels, + sr, + **kwargs + ): + super(SynthesizerTrnMs256NSFsid, self).__init__() + self.spec_channels = spec_channels + self.inter_channels = inter_channels + self.hidden_channels = hidden_channels + self.filter_channels = filter_channels + self.n_heads = n_heads + self.n_layers = n_layers + self.kernel_size = kernel_size + self.p_dropout = float(p_dropout) + self.resblock = resblock + self.resblock_kernel_sizes = resblock_kernel_sizes + self.resblock_dilation_sizes = resblock_dilation_sizes + self.upsample_rates = upsample_rates + self.upsample_initial_channel = upsample_initial_channel + self.upsample_kernel_sizes = upsample_kernel_sizes + self.segment_size = segment_size + self.gin_channels = gin_channels + # self.hop_length = hop_length# + self.spk_embed_dim = spk_embed_dim + self.enc_p = TextEncoder256( + inter_channels, + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size, + float(p_dropout), + ) + self.dec = GeneratorNSF( + inter_channels, + resblock, + resblock_kernel_sizes, + resblock_dilation_sizes, + upsample_rates, + upsample_initial_channel, + upsample_kernel_sizes, + gin_channels=gin_channels, + sr=sr, + is_half=kwargs["is_half"], + ) + self.enc_q = PosteriorEncoder( + spec_channels, + inter_channels, + hidden_channels, + 5, + 1, + 16, + gin_channels=gin_channels, + ) + self.flow = ResidualCouplingBlock( + inter_channels, hidden_channels, 5, 1, 3, gin_channels=gin_channels + ) + self.emb_g = nn.Embedding(self.spk_embed_dim, gin_channels) + + def remove_weight_norm(self): + self.dec.remove_weight_norm() + self.flow.remove_weight_norm() + self.enc_q.remove_weight_norm() + + def __prepare_scriptable__(self): + for hook in self.dec._forward_pre_hooks.values(): + # The hook we want to remove is an instance of WeightNorm class, so + # normally we would do `if isinstance(...)` but this class is not accessible + # because of shadowing, so we check the module name directly. + # https://github.com/pytorch/pytorch/blob/be0ca00c5ce260eb5bcec3237357f7a30cc08983/torch/nn/utils/__init__.py#L3 + if ( + hook.__module__ == "torch.nn.utils.parametrizations.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(self.dec) + for hook in self.flow._forward_pre_hooks.values(): + if ( + hook.__module__ == "torch.nn.utils.parametrizations.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(self.flow) + if hasattr(self, "enc_q"): + for hook in self.enc_q._forward_pre_hooks.values(): + if ( + hook.__module__ == "torch.nn.utils.parametrizations.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(self.enc_q) + return self + + @torch.jit.ignore + def forward( + self, + phone: torch.Tensor, + phone_lengths: torch.Tensor, + pitch: torch.Tensor, + pitchf: torch.Tensor, + y: torch.Tensor, + y_lengths: torch.Tensor, + ds: Optional[torch.Tensor] = None, + ): # 这里ds是id,[bs,1] + # print(1,pitch.shape)#[bs,t] + g = self.emb_g(ds).unsqueeze(-1) # [b, 256, 1]##1是t,广播的 + m_p, logs_p, x_mask = self.enc_p(phone, pitch, phone_lengths) + z, m_q, logs_q, y_mask = self.enc_q(y, y_lengths, g=g) + z_p = self.flow(z, y_mask, g=g) + z_slice, ids_slice = commons.rand_slice_segments( + z, y_lengths, self.segment_size + ) + # print(-1,pitchf.shape,ids_slice,self.segment_size,self.hop_length,self.segment_size//self.hop_length) + pitchf = commons.slice_segments2(pitchf, ids_slice, self.segment_size) + # print(-2,pitchf.shape,z_slice.shape) + o = self.dec(z_slice, pitchf, g=g) + return o, ids_slice, x_mask, y_mask, (z, z_p, m_p, logs_p, m_q, logs_q) + + @torch.jit.export + def infer( + self, + phone: torch.Tensor, + phone_lengths: torch.Tensor, + pitch: torch.Tensor, + nsff0: torch.Tensor, + sid: torch.Tensor, + rate: Optional[torch.Tensor] = None, + ): + g = self.emb_g(sid).unsqueeze(-1) + m_p, logs_p, x_mask = self.enc_p(phone, pitch, phone_lengths) + z_p = (m_p + torch.exp(logs_p) * torch.randn_like(m_p) * 0.66666) * x_mask + if rate is not None: + assert isinstance(rate, torch.Tensor) + head = int(z_p.shape[2] * (1 - rate.item())) + z_p = z_p[:, :, head:] + x_mask = x_mask[:, :, head:] + nsff0 = nsff0[:, head:] + z = self.flow(z_p, x_mask, g=g, reverse=True) + o = self.dec(z * x_mask, nsff0, g=g) + return o, x_mask, (z, z_p, m_p, logs_p) + + +class SynthesizerTrnMs768NSFsid(nn.Module): + def __init__( + self, + spec_channels, + segment_size, + inter_channels, + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size, + p_dropout, + resblock, + resblock_kernel_sizes, + resblock_dilation_sizes, + upsample_rates, + upsample_initial_channel, + upsample_kernel_sizes, + spk_embed_dim, + gin_channels, + sr, + **kwargs + ): + super(SynthesizerTrnMs768NSFsid, self).__init__() + self.spec_channels = spec_channels + self.inter_channels = inter_channels + self.hidden_channels = hidden_channels + self.filter_channels = filter_channels + self.n_heads = n_heads + self.n_layers = n_layers + self.kernel_size = kernel_size + self.p_dropout = float(p_dropout) + self.resblock = resblock + self.resblock_kernel_sizes = resblock_kernel_sizes + self.resblock_dilation_sizes = resblock_dilation_sizes + self.upsample_rates = upsample_rates + self.upsample_initial_channel = upsample_initial_channel + self.upsample_kernel_sizes = upsample_kernel_sizes + self.segment_size = segment_size + self.gin_channels = gin_channels + # self.hop_length = hop_length# + self.spk_embed_dim = spk_embed_dim + self.enc_p = TextEncoder768( + inter_channels, + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size, + float(p_dropout), + ) + self.dec = GeneratorNSF( + inter_channels, + resblock, + resblock_kernel_sizes, + resblock_dilation_sizes, + upsample_rates, + upsample_initial_channel, + upsample_kernel_sizes, + gin_channels=gin_channels, + sr=sr, + is_half=kwargs["is_half"], + ) + self.enc_q = PosteriorEncoder( + spec_channels, + inter_channels, + hidden_channels, + 5, + 1, + 16, + gin_channels=gin_channels, + ) + self.flow = ResidualCouplingBlock( + inter_channels, hidden_channels, 5, 1, 3, gin_channels=gin_channels + ) + self.emb_g = nn.Embedding(self.spk_embed_dim, gin_channels) + + def remove_weight_norm(self): + self.dec.remove_weight_norm() + self.flow.remove_weight_norm() + self.enc_q.remove_weight_norm() + + def __prepare_scriptable__(self): + for hook in self.dec._forward_pre_hooks.values(): + # The hook we want to remove is an instance of WeightNorm class, so + # normally we would do `if isinstance(...)` but this class is not accessible + # because of shadowing, so we check the module name directly. + # https://github.com/pytorch/pytorch/blob/be0ca00c5ce260eb5bcec3237357f7a30cc08983/torch/nn/utils/__init__.py#L3 + if ( + hook.__module__ == "torch.nn.utils.parametrizations.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(self.dec) + for hook in self.flow._forward_pre_hooks.values(): + if ( + hook.__module__ == "torch.nn.utils.parametrizations.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(self.flow) + if hasattr(self, "enc_q"): + for hook in self.enc_q._forward_pre_hooks.values(): + if ( + hook.__module__ == "torch.nn.utils.parametrizations.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(self.enc_q) + return self + + @torch.jit.ignore + def forward( + self, phone, phone_lengths, pitch, pitchf, y, y_lengths, ds + ): # 这里ds是id,[bs,1] + # print(1,pitch.shape)#[bs,t] + g = self.emb_g(ds).unsqueeze(-1) # [b, 256, 1]##1是t,广播的 + m_p, logs_p, x_mask = self.enc_p(phone, pitch, phone_lengths) + z, m_q, logs_q, y_mask = self.enc_q(y, y_lengths, g=g) + z_p = self.flow(z, y_mask, g=g) + z_slice, ids_slice = commons.rand_slice_segments( + z, y_lengths, self.segment_size + ) + # print(-1,pitchf.shape,ids_slice,self.segment_size,self.hop_length,self.segment_size//self.hop_length) + pitchf = commons.slice_segments2(pitchf, ids_slice, self.segment_size) + # print(-2,pitchf.shape,z_slice.shape) + o = self.dec(z_slice, pitchf, g=g) + return o, ids_slice, x_mask, y_mask, (z, z_p, m_p, logs_p, m_q, logs_q) + + @torch.jit.export + def infer( + self, + phone: torch.Tensor, + phone_lengths: torch.Tensor, + pitch: torch.Tensor, + nsff0: torch.Tensor, + sid: torch.Tensor, + rate: Optional[torch.Tensor] = None, + ): + g = self.emb_g(sid).unsqueeze(-1) + m_p, logs_p, x_mask = self.enc_p(phone, pitch, phone_lengths) + z_p = (m_p + torch.exp(logs_p) * torch.randn_like(m_p) * 0.66666) * x_mask + if rate is not None: + head = int(z_p.shape[2] * (1.0 - rate.item())) + z_p = z_p[:, :, head:] + x_mask = x_mask[:, :, head:] + nsff0 = nsff0[:, head:] + z = self.flow(z_p, x_mask, g=g, reverse=True) + o = self.dec(z * x_mask, nsff0, g=g) + return o, x_mask, (z, z_p, m_p, logs_p) + + +class SynthesizerTrnMs256NSFsid_nono(nn.Module): + def __init__( + self, + spec_channels, + segment_size, + inter_channels, + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size, + p_dropout, + resblock, + resblock_kernel_sizes, + resblock_dilation_sizes, + upsample_rates, + upsample_initial_channel, + upsample_kernel_sizes, + spk_embed_dim, + gin_channels, + sr=None, + **kwargs + ): + super(SynthesizerTrnMs256NSFsid_nono, self).__init__() + self.spec_channels = spec_channels + self.inter_channels = inter_channels + self.hidden_channels = hidden_channels + self.filter_channels = filter_channels + self.n_heads = n_heads + self.n_layers = n_layers + self.kernel_size = kernel_size + self.p_dropout = float(p_dropout) + self.resblock = resblock + self.resblock_kernel_sizes = resblock_kernel_sizes + self.resblock_dilation_sizes = resblock_dilation_sizes + self.upsample_rates = upsample_rates + self.upsample_initial_channel = upsample_initial_channel + self.upsample_kernel_sizes = upsample_kernel_sizes + self.segment_size = segment_size + self.gin_channels = gin_channels + # self.hop_length = hop_length# + self.spk_embed_dim = spk_embed_dim + self.enc_p = TextEncoder256( + inter_channels, + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size, + float(p_dropout), + f0=False, + ) + self.dec = Generator( + inter_channels, + resblock, + resblock_kernel_sizes, + resblock_dilation_sizes, + upsample_rates, + upsample_initial_channel, + upsample_kernel_sizes, + gin_channels=gin_channels, + ) + self.enc_q = PosteriorEncoder( + spec_channels, + inter_channels, + hidden_channels, + 5, + 1, + 16, + gin_channels=gin_channels, + ) + self.flow = ResidualCouplingBlock( + inter_channels, hidden_channels, 5, 1, 3, gin_channels=gin_channels + ) + self.emb_g = nn.Embedding(self.spk_embed_dim, gin_channels) + + def remove_weight_norm(self): + self.dec.remove_weight_norm() + self.flow.remove_weight_norm() + self.enc_q.remove_weight_norm() + + def __prepare_scriptable__(self): + for hook in self.dec._forward_pre_hooks.values(): + # The hook we want to remove is an instance of WeightNorm class, so + # normally we would do `if isinstance(...)` but this class is not accessible + # because of shadowing, so we check the module name directly. + # https://github.com/pytorch/pytorch/blob/be0ca00c5ce260eb5bcec3237357f7a30cc08983/torch/nn/utils/__init__.py#L3 + if ( + hook.__module__ == "torch.nn.utils.parametrizations.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(self.dec) + for hook in self.flow._forward_pre_hooks.values(): + if ( + hook.__module__ == "torch.nn.utils.parametrizations.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(self.flow) + if hasattr(self, "enc_q"): + for hook in self.enc_q._forward_pre_hooks.values(): + if ( + hook.__module__ == "torch.nn.utils.parametrizations.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(self.enc_q) + return self + + @torch.jit.ignore + def forward(self, phone, phone_lengths, y, y_lengths, ds): # 这里ds是id,[bs,1] + g = self.emb_g(ds).unsqueeze(-1) # [b, 256, 1]##1是t,广播的 + m_p, logs_p, x_mask = self.enc_p(phone, None, phone_lengths) + z, m_q, logs_q, y_mask = self.enc_q(y, y_lengths, g=g) + z_p = self.flow(z, y_mask, g=g) + z_slice, ids_slice = commons.rand_slice_segments( + z, y_lengths, self.segment_size + ) + o = self.dec(z_slice, g=g) + return o, ids_slice, x_mask, y_mask, (z, z_p, m_p, logs_p, m_q, logs_q) + + @torch.jit.export + def infer( + self, + phone: torch.Tensor, + phone_lengths: torch.Tensor, + sid: torch.Tensor, + rate: Optional[torch.Tensor] = None, + ): + g = self.emb_g(sid).unsqueeze(-1) + m_p, logs_p, x_mask = self.enc_p(phone, None, phone_lengths) + z_p = (m_p + torch.exp(logs_p) * torch.randn_like(m_p) * 0.66666) * x_mask + if rate is not None: + head = int(z_p.shape[2] * (1.0 - rate.item())) + z_p = z_p[:, :, head:] + x_mask = x_mask[:, :, head:] + z = self.flow(z_p, x_mask, g=g, reverse=True) + o = self.dec(z * x_mask, g=g) + return o, x_mask, (z, z_p, m_p, logs_p) + + +class SynthesizerTrnMs768NSFsid_nono(nn.Module): + def __init__( + self, + spec_channels, + segment_size, + inter_channels, + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size, + p_dropout, + resblock, + resblock_kernel_sizes, + resblock_dilation_sizes, + upsample_rates, + upsample_initial_channel, + upsample_kernel_sizes, + spk_embed_dim, + gin_channels, + sr=None, + **kwargs + ): + super(SynthesizerTrnMs768NSFsid_nono, self).__init__() + self.spec_channels = spec_channels + self.inter_channels = inter_channels + self.hidden_channels = hidden_channels + self.filter_channels = filter_channels + self.n_heads = n_heads + self.n_layers = n_layers + self.kernel_size = kernel_size + self.p_dropout = float(p_dropout) + self.resblock = resblock + self.resblock_kernel_sizes = resblock_kernel_sizes + self.resblock_dilation_sizes = resblock_dilation_sizes + self.upsample_rates = upsample_rates + self.upsample_initial_channel = upsample_initial_channel + self.upsample_kernel_sizes = upsample_kernel_sizes + self.segment_size = segment_size + self.gin_channels = gin_channels + # self.hop_length = hop_length# + self.spk_embed_dim = spk_embed_dim + self.enc_p = TextEncoder768( + inter_channels, + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size, + float(p_dropout), + f0=False, + ) + self.dec = Generator( + inter_channels, + resblock, + resblock_kernel_sizes, + resblock_dilation_sizes, + upsample_rates, + upsample_initial_channel, + upsample_kernel_sizes, + gin_channels=gin_channels, + ) + self.enc_q = PosteriorEncoder( + spec_channels, + inter_channels, + hidden_channels, + 5, + 1, + 16, + gin_channels=gin_channels, + ) + self.flow = ResidualCouplingBlock( + inter_channels, hidden_channels, 5, 1, 3, gin_channels=gin_channels + ) + self.emb_g = nn.Embedding(self.spk_embed_dim, gin_channels) + + def remove_weight_norm(self): + self.dec.remove_weight_norm() + self.flow.remove_weight_norm() + self.enc_q.remove_weight_norm() + + def __prepare_scriptable__(self): + for hook in self.dec._forward_pre_hooks.values(): + # The hook we want to remove is an instance of WeightNorm class, so + # normally we would do `if isinstance(...)` but this class is not accessible + # because of shadowing, so we check the module name directly. + # https://github.com/pytorch/pytorch/blob/be0ca00c5ce260eb5bcec3237357f7a30cc08983/torch/nn/utils/__init__.py#L3 + if ( + hook.__module__ == "torch.nn.utils.parametrizations.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(self.dec) + for hook in self.flow._forward_pre_hooks.values(): + if ( + hook.__module__ == "torch.nn.utils.parametrizations.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(self.flow) + if hasattr(self, "enc_q"): + for hook in self.enc_q._forward_pre_hooks.values(): + if ( + hook.__module__ == "torch.nn.utils.parametrizations.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(self.enc_q) + return self + + @torch.jit.ignore + def forward(self, phone, phone_lengths, y, y_lengths, ds): # 这里ds是id,[bs,1] + g = self.emb_g(ds).unsqueeze(-1) # [b, 256, 1]##1是t,广播的 + m_p, logs_p, x_mask = self.enc_p(phone, None, phone_lengths) + z, m_q, logs_q, y_mask = self.enc_q(y, y_lengths, g=g) + z_p = self.flow(z, y_mask, g=g) + z_slice, ids_slice = commons.rand_slice_segments( + z, y_lengths, self.segment_size + ) + o = self.dec(z_slice, g=g) + return o, ids_slice, x_mask, y_mask, (z, z_p, m_p, logs_p, m_q, logs_q) + + @torch.jit.export + def infer( + self, + phone: torch.Tensor, + phone_lengths: torch.Tensor, + sid: torch.Tensor, + rate: Optional[torch.Tensor] = None, + ): + g = self.emb_g(sid).unsqueeze(-1) + m_p, logs_p, x_mask = self.enc_p(phone, None, phone_lengths) + z_p = (m_p + torch.exp(logs_p) * torch.randn_like(m_p) * 0.66666) * x_mask + if rate is not None: + head = int(z_p.shape[2] * (1.0 - rate.item())) + z_p = z_p[:, :, head:] + x_mask = x_mask[:, :, head:] + z = self.flow(z_p, x_mask, g=g, reverse=True) + o = self.dec(z * x_mask, g=g) + return o, x_mask, (z, z_p, m_p, logs_p) + + +class MultiPeriodDiscriminator(torch.nn.Module): + def __init__(self, use_spectral_norm=False): + super(MultiPeriodDiscriminator, self).__init__() + periods = [2, 3, 5, 7, 11, 17] + # periods = [3, 5, 7, 11, 17, 23, 37] + + discs = [DiscriminatorS(use_spectral_norm=use_spectral_norm)] + discs = discs + [ + DiscriminatorP(i, use_spectral_norm=use_spectral_norm) for i in periods + ] + self.discriminators = nn.ModuleList(discs) + + def forward(self, y, y_hat): + y_d_rs = [] # + y_d_gs = [] + fmap_rs = [] + fmap_gs = [] + for i, d in enumerate(self.discriminators): + y_d_r, fmap_r = d(y) + y_d_g, fmap_g = d(y_hat) + # for j in range(len(fmap_r)): + # print(i,j,y.shape,y_hat.shape,fmap_r[j].shape,fmap_g[j].shape) + y_d_rs.append(y_d_r) + y_d_gs.append(y_d_g) + fmap_rs.append(fmap_r) + fmap_gs.append(fmap_g) + + return y_d_rs, y_d_gs, fmap_rs, fmap_gs + + +class MultiPeriodDiscriminatorV2(torch.nn.Module): + def __init__(self, use_spectral_norm=False): + super(MultiPeriodDiscriminatorV2, self).__init__() + # periods = [2, 3, 5, 7, 11, 17] + periods = [2, 3, 5, 7, 11, 17, 23, 37] + + discs = [DiscriminatorS(use_spectral_norm=use_spectral_norm)] + discs = discs + [ + DiscriminatorP(i, use_spectral_norm=use_spectral_norm) for i in periods + ] + self.discriminators = nn.ModuleList(discs) + + def forward(self, y, y_hat): + y_d_rs = [] # + y_d_gs = [] + fmap_rs = [] + fmap_gs = [] + for i, d in enumerate(self.discriminators): + y_d_r, fmap_r = d(y) + y_d_g, fmap_g = d(y_hat) + # for j in range(len(fmap_r)): + # print(i,j,y.shape,y_hat.shape,fmap_r[j].shape,fmap_g[j].shape) + y_d_rs.append(y_d_r) + y_d_gs.append(y_d_g) + fmap_rs.append(fmap_r) + fmap_gs.append(fmap_g) + + return y_d_rs, y_d_gs, fmap_rs, fmap_gs + + +class DiscriminatorS(torch.nn.Module): + def __init__(self, use_spectral_norm=False): + super(DiscriminatorS, self).__init__() + norm_f = weight_norm if use_spectral_norm == False else spectral_norm + self.convs = nn.ModuleList( + [ + norm_f(Conv1d(1, 16, 15, 1, padding=7)), + norm_f(Conv1d(16, 64, 41, 4, groups=4, padding=20)), + norm_f(Conv1d(64, 256, 41, 4, groups=16, padding=20)), + norm_f(Conv1d(256, 1024, 41, 4, groups=64, padding=20)), + norm_f(Conv1d(1024, 1024, 41, 4, groups=256, padding=20)), + norm_f(Conv1d(1024, 1024, 5, 1, padding=2)), + ] + ) + self.conv_post = norm_f(Conv1d(1024, 1, 3, 1, padding=1)) + + def forward(self, x): + fmap = [] + + for l in self.convs: + x = l(x) + x = F.leaky_relu(x, modules.LRELU_SLOPE) + fmap.append(x) + x = self.conv_post(x) + fmap.append(x) + x = torch.flatten(x, 1, -1) + + return x, fmap + + +class DiscriminatorP(torch.nn.Module): + def __init__(self, period, kernel_size=5, stride=3, use_spectral_norm=False): + super(DiscriminatorP, self).__init__() + self.period = period + self.use_spectral_norm = use_spectral_norm + norm_f = weight_norm if use_spectral_norm == False else spectral_norm + self.convs = nn.ModuleList( + [ + norm_f( + Conv2d( + 1, + 32, + (kernel_size, 1), + (stride, 1), + padding=(get_padding(kernel_size, 1), 0), + ) + ), + norm_f( + Conv2d( + 32, + 128, + (kernel_size, 1), + (stride, 1), + padding=(get_padding(kernel_size, 1), 0), + ) + ), + norm_f( + Conv2d( + 128, + 512, + (kernel_size, 1), + (stride, 1), + padding=(get_padding(kernel_size, 1), 0), + ) + ), + norm_f( + Conv2d( + 512, + 1024, + (kernel_size, 1), + (stride, 1), + padding=(get_padding(kernel_size, 1), 0), + ) + ), + norm_f( + Conv2d( + 1024, + 1024, + (kernel_size, 1), + 1, + padding=(get_padding(kernel_size, 1), 0), + ) + ), + ] + ) + self.conv_post = norm_f(Conv2d(1024, 1, (3, 1), 1, padding=(1, 0))) + + def forward(self, x): + fmap = [] + + # 1d to 2d + b, c, t = x.shape + if t % self.period != 0: # pad first + n_pad = self.period - (t % self.period) + if has_xpu and x.dtype == torch.bfloat16: + x = F.pad(x.to(dtype=torch.float16), (0, n_pad), "reflect").to( + dtype=torch.bfloat16 + ) + else: + x = F.pad(x, (0, n_pad), "reflect") + t = t + n_pad + x = x.view(b, c, t // self.period, self.period) + + for l in self.convs: + x = l(x) + x = F.leaky_relu(x, modules.LRELU_SLOPE) + fmap.append(x) + x = self.conv_post(x) + fmap.append(x) + x = torch.flatten(x, 1, -1) + + return x, fmap diff --git a/system/tts_engines/rvc/lib/infer_pack/modules.py b/system/tts_engines/rvc/lib/infer_pack/modules.py new file mode 100644 index 0000000..e8d5e91 --- /dev/null +++ b/system/tts_engines/rvc/lib/infer_pack/modules.py @@ -0,0 +1,517 @@ +import math +import torch +from torch import nn +from torch.nn import functional as F + +from torch.nn import Conv1d +from torch.nn.utils import remove_weight_norm +from torch.nn.utils.parametrizations import weight_norm # Updated import + +from . import commons +from .commons import init_weights, get_padding +from .transforms import piecewise_rational_quadratic_transform + + +LRELU_SLOPE = 0.1 + + +class LayerNorm(nn.Module): + def __init__(self, channels, eps=1e-5): + super().__init__() + self.channels = channels + self.eps = eps + + self.gamma = nn.Parameter(torch.ones(channels)) + self.beta = nn.Parameter(torch.zeros(channels)) + + def forward(self, x): + x = x.transpose(1, -1) + x = F.layer_norm(x, (self.channels,), self.gamma, self.beta, self.eps) + return x.transpose(1, -1) + + +class ConvReluNorm(nn.Module): + def __init__( + self, + in_channels, + hidden_channels, + out_channels, + kernel_size, + n_layers, + p_dropout, + ): + super().__init__() + self.in_channels = in_channels + self.hidden_channels = hidden_channels + self.out_channels = out_channels + self.kernel_size = kernel_size + self.n_layers = n_layers + self.p_dropout = p_dropout + assert n_layers > 1, "Number of layers should be larger than 0." + + self.conv_layers = nn.ModuleList() + self.norm_layers = nn.ModuleList() + self.conv_layers.append( + nn.Conv1d( + in_channels, hidden_channels, kernel_size, padding=kernel_size // 2 + ) + ) + self.norm_layers.append(LayerNorm(hidden_channels)) + self.relu_drop = nn.Sequential(nn.ReLU(), nn.Dropout(p_dropout)) + for _ in range(n_layers - 1): + self.conv_layers.append( + nn.Conv1d( + hidden_channels, + hidden_channels, + kernel_size, + padding=kernel_size // 2, + ) + ) + self.norm_layers.append(LayerNorm(hidden_channels)) + self.proj = nn.Conv1d(hidden_channels, out_channels, 1) + self.proj.weight.data.zero_() + self.proj.bias.data.zero_() + + def forward(self, x, x_mask): + x_org = x + for i in range(self.n_layers): + x = self.conv_layers[i](x * x_mask) + x = self.norm_layers[i](x) + x = self.relu_drop(x) + x = x_org + self.proj(x) + return x * x_mask + + +class DDSConv(nn.Module): + def __init__(self, channels, kernel_size, n_layers, p_dropout=0.0): + super().__init__() + self.channels = channels + self.kernel_size = kernel_size + self.n_layers = n_layers + self.p_dropout = p_dropout + + self.drop = nn.Dropout(p_dropout) + self.convs_sep = nn.ModuleList() + self.convs_1x1 = nn.ModuleList() + self.norms_1 = nn.ModuleList() + self.norms_2 = nn.ModuleList() + for i in range(n_layers): + dilation = kernel_size**i + padding = (kernel_size * dilation - dilation) // 2 + self.convs_sep.append( + nn.Conv1d( + channels, + channels, + kernel_size, + groups=channels, + dilation=dilation, + padding=padding, + ) + ) + self.convs_1x1.append(nn.Conv1d(channels, channels, 1)) + self.norms_1.append(LayerNorm(channels)) + self.norms_2.append(LayerNorm(channels)) + + def forward(self, x, x_mask, g=None): + if g is not None: + x = x + g + for i in range(self.n_layers): + y = self.convs_sep[i](x * x_mask) + y = self.norms_1[i](y) + y = F.gelu(y) + y = self.convs_1x1[i](y) + y = self.norms_2[i](y) + y = F.gelu(y) + y = self.drop(y) + x = x + y + return x * x_mask + + +class WN(torch.nn.Module): + def __init__( + self, + hidden_channels, + kernel_size, + dilation_rate, + n_layers, + gin_channels=0, + p_dropout=0, + ): + super(WN, self).__init__() + assert kernel_size % 2 == 1 + self.hidden_channels = hidden_channels + self.kernel_size = (kernel_size,) + self.dilation_rate = dilation_rate + self.n_layers = n_layers + self.gin_channels = gin_channels + self.p_dropout = p_dropout + + self.in_layers = torch.nn.ModuleList() + self.res_skip_layers = torch.nn.ModuleList() + self.drop = nn.Dropout(p_dropout) + + if gin_channels != 0: + cond_layer = torch.nn.Conv1d( + gin_channels, 2 * hidden_channels * n_layers, 1 + ) + #self.cond_layer = torch.nn.utils.parametrizations.weight_norm(cond_layer, name="weight") + self.cond_layer = weight_norm(cond_layer, name="weight") # Updated to use the new import + + for i in range(n_layers): + dilation = dilation_rate**i + padding = int((kernel_size * dilation - dilation) / 2) + in_layer = torch.nn.Conv1d( + hidden_channels, + 2 * hidden_channels, + kernel_size, + dilation=dilation, + padding=padding, + ) + #in_layer = torch.nn.utils.parametrizations.weight_norm(in_layer, name="weight") + in_layer = weight_norm(in_layer, name="weight") # Updated to use the new impor + self.in_layers.append(in_layer) + if i < n_layers - 1: + res_skip_channels = 2 * hidden_channels + else: + res_skip_channels = hidden_channels + + res_skip_layer = torch.nn.Conv1d(hidden_channels, res_skip_channels, 1) + #res_skip_layer = torch.nn.utils.parametrizations.weight_norm(res_skip_layer, name="weight") + res_skip_layer = weight_norm(res_skip_layer, name="weight") # Updated to use the new import + self.res_skip_layers.append(res_skip_layer) + + def forward(self, x, x_mask, g=None, **kwargs): + output = torch.zeros_like(x) + n_channels_tensor = torch.IntTensor([self.hidden_channels]) + + if g is not None: + g = self.cond_layer(g) + + for i in range(self.n_layers): + x_in = self.in_layers[i](x) + if g is not None: + cond_offset = i * 2 * self.hidden_channels + g_l = g[:, cond_offset : cond_offset + 2 * self.hidden_channels, :] + else: + g_l = torch.zeros_like(x_in) + + acts = commons.fused_add_tanh_sigmoid_multiply(x_in, g_l, n_channels_tensor) + acts = self.drop(acts) + + res_skip_acts = self.res_skip_layers[i](acts) + if i < self.n_layers - 1: + res_acts = res_skip_acts[:, : self.hidden_channels, :] + x = (x + res_acts) * x_mask + output = output + res_skip_acts[:, self.hidden_channels :, :] + else: + output = output + res_skip_acts + return output * x_mask + + def remove_weight_norm(self): + if self.gin_channels != 0: + torch.nn.utils.remove_weight_norm(self.cond_layer) + for l in self.in_layers: + torch.nn.utils.remove_weight_norm(l) + for l in self.res_skip_layers: + torch.nn.utils.remove_weight_norm(l) + + +class ResBlock1(torch.nn.Module): + def __init__(self, channels, kernel_size=3, dilation=(1, 3, 5)): + super(ResBlock1, self).__init__() + self.convs1 = nn.ModuleList( + [ + weight_norm( + Conv1d( + channels, + channels, + kernel_size, + 1, + dilation=dilation[0], + padding=get_padding(kernel_size, dilation[0]), + ) + ), + weight_norm( + Conv1d( + channels, + channels, + kernel_size, + 1, + dilation=dilation[1], + padding=get_padding(kernel_size, dilation[1]), + ) + ), + weight_norm( + Conv1d( + channels, + channels, + kernel_size, + 1, + dilation=dilation[2], + padding=get_padding(kernel_size, dilation[2]), + ) + ), + ] + ) + self.convs1.apply(init_weights) + + self.convs2 = nn.ModuleList( + [ + weight_norm( + Conv1d( + channels, + channels, + kernel_size, + 1, + dilation=1, + padding=get_padding(kernel_size, 1), + ) + ), + weight_norm( + Conv1d( + channels, + channels, + kernel_size, + 1, + dilation=1, + padding=get_padding(kernel_size, 1), + ) + ), + weight_norm( + Conv1d( + channels, + channels, + kernel_size, + 1, + dilation=1, + padding=get_padding(kernel_size, 1), + ) + ), + ] + ) + self.convs2.apply(init_weights) + + def forward(self, x, x_mask=None): + for c1, c2 in zip(self.convs1, self.convs2): + xt = F.leaky_relu(x, LRELU_SLOPE) + if x_mask is not None: + xt = xt * x_mask + xt = c1(xt) + xt = F.leaky_relu(xt, LRELU_SLOPE) + if x_mask is not None: + xt = xt * x_mask + xt = c2(xt) + x = xt + x + if x_mask is not None: + x = x * x_mask + return x + + def remove_weight_norm(self): + for l in self.convs1: + remove_weight_norm(l) + for l in self.convs2: + remove_weight_norm(l) + + +class ResBlock2(torch.nn.Module): + def __init__(self, channels, kernel_size=3, dilation=(1, 3)): + super(ResBlock2, self).__init__() + self.convs = nn.ModuleList( + [ + weight_norm( + Conv1d( + channels, + channels, + kernel_size, + 1, + dilation=dilation[0], + padding=get_padding(kernel_size, dilation[0]), + ) + ), + weight_norm( + Conv1d( + channels, + channels, + kernel_size, + 1, + dilation=dilation[1], + padding=get_padding(kernel_size, dilation[1]), + ) + ), + ] + ) + self.convs.apply(init_weights) + + def forward(self, x, x_mask=None): + for c in self.convs: + xt = F.leaky_relu(x, LRELU_SLOPE) + if x_mask is not None: + xt = xt * x_mask + xt = c(xt) + x = xt + x + if x_mask is not None: + x = x * x_mask + return x + + def remove_weight_norm(self): + for l in self.convs: + remove_weight_norm(l) + + +class Log(nn.Module): + def forward(self, x, x_mask, reverse=False, **kwargs): + if not reverse: + y = torch.log(torch.clamp_min(x, 1e-5)) * x_mask + logdet = torch.sum(-y, [1, 2]) + return y, logdet + else: + x = torch.exp(x) * x_mask + return x + + +class Flip(nn.Module): + def forward(self, x, *args, reverse=False, **kwargs): + x = torch.flip(x, [1]) + if not reverse: + logdet = torch.zeros(x.size(0)).to(dtype=x.dtype, device=x.device) + return x, logdet + else: + return x + + +class ElementwiseAffine(nn.Module): + def __init__(self, channels): + super().__init__() + self.channels = channels + self.m = nn.Parameter(torch.zeros(channels, 1)) + self.logs = nn.Parameter(torch.zeros(channels, 1)) + + def forward(self, x, x_mask, reverse=False, **kwargs): + if not reverse: + y = self.m + torch.exp(self.logs) * x + y = y * x_mask + logdet = torch.sum(self.logs * x_mask, [1, 2]) + return y, logdet + else: + x = (x - self.m) * torch.exp(-self.logs) * x_mask + return x + + +class ResidualCouplingLayer(nn.Module): + def __init__( + self, + channels, + hidden_channels, + kernel_size, + dilation_rate, + n_layers, + p_dropout=0, + gin_channels=0, + mean_only=False, + ): + assert channels % 2 == 0, "channels should be divisible by 2" + super().__init__() + self.channels = channels + self.hidden_channels = hidden_channels + self.kernel_size = kernel_size + self.dilation_rate = dilation_rate + self.n_layers = n_layers + self.half_channels = channels // 2 + self.mean_only = mean_only + + self.pre = nn.Conv1d(self.half_channels, hidden_channels, 1) + self.enc = WN( + hidden_channels, + kernel_size, + dilation_rate, + n_layers, + p_dropout=p_dropout, + gin_channels=gin_channels, + ) + self.post = nn.Conv1d(hidden_channels, self.half_channels * (2 - mean_only), 1) + self.post.weight.data.zero_() + self.post.bias.data.zero_() + + def forward(self, x, x_mask, g=None, reverse=False): + x0, x1 = torch.split(x, [self.half_channels] * 2, 1) + h = self.pre(x0) * x_mask + h = self.enc(h, x_mask, g=g) + stats = self.post(h) * x_mask + if not self.mean_only: + m, logs = torch.split(stats, [self.half_channels] * 2, 1) + else: + m = stats + logs = torch.zeros_like(m) + + if not reverse: + x1 = m + x1 * torch.exp(logs) * x_mask + x = torch.cat([x0, x1], 1) + logdet = torch.sum(logs, [1, 2]) + return x, logdet + else: + x1 = (x1 - m) * torch.exp(-logs) * x_mask + x = torch.cat([x0, x1], 1) + return x + + def remove_weight_norm(self): + self.enc.remove_weight_norm() + + +class ConvFlow(nn.Module): + def __init__( + self, + in_channels, + filter_channels, + kernel_size, + n_layers, + num_bins=10, + tail_bound=5.0, + ): + super().__init__() + self.in_channels = in_channels + self.filter_channels = filter_channels + self.kernel_size = kernel_size + self.n_layers = n_layers + self.num_bins = num_bins + self.tail_bound = tail_bound + self.half_channels = in_channels // 2 + + self.pre = nn.Conv1d(self.half_channels, filter_channels, 1) + self.convs = DDSConv(filter_channels, kernel_size, n_layers, p_dropout=0.0) + self.proj = nn.Conv1d( + filter_channels, self.half_channels * (num_bins * 3 - 1), 1 + ) + self.proj.weight.data.zero_() + self.proj.bias.data.zero_() + + def forward(self, x, x_mask, g=None, reverse=False): + x0, x1 = torch.split(x, [self.half_channels] * 2, 1) + h = self.pre(x0) + h = self.convs(h, x_mask, g=g) + h = self.proj(h) * x_mask + + b, c, t = x0.shape + h = h.reshape(b, c, -1, t).permute(0, 1, 3, 2) + + unnormalized_widths = h[..., : self.num_bins] / math.sqrt(self.filter_channels) + unnormalized_heights = h[..., self.num_bins : 2 * self.num_bins] / math.sqrt( + self.filter_channels + ) + unnormalized_derivatives = h[..., 2 * self.num_bins :] + + x1, logabsdet = piecewise_rational_quadratic_transform( + x1, + unnormalized_widths, + unnormalized_heights, + unnormalized_derivatives, + inverse=reverse, + tails="linear", + tail_bound=self.tail_bound, + ) + + x = torch.cat([x0, x1], 1) * x_mask + logdet = torch.sum(logabsdet * x_mask, [1, 2]) + if not reverse: + return x, logdet + else: + return x diff --git a/system/tts_engines/rvc/lib/infer_pack/modules/F0Predictor/DioF0Predictor.py b/system/tts_engines/rvc/lib/infer_pack/modules/F0Predictor/DioF0Predictor.py new file mode 100644 index 0000000..6d241b4 --- /dev/null +++ b/system/tts_engines/rvc/lib/infer_pack/modules/F0Predictor/DioF0Predictor.py @@ -0,0 +1,86 @@ +from infer_pack.modules.F0Predictor.F0Predictor import F0Predictor +import pyworld +import numpy as np + + +class DioF0Predictor(F0Predictor): + def __init__(self, hop_length=512, f0_min=50, f0_max=1100, sampling_rate=44100): + self.hop_length = hop_length + self.f0_min = f0_min + self.f0_max = f0_max + self.sampling_rate = sampling_rate + + def interpolate_f0(self, f0): + data = np.reshape(f0, (f0.size, 1)) + + vuv_vector = np.zeros((data.size, 1), dtype=np.float32) + vuv_vector[data > 0.0] = 1.0 + vuv_vector[data <= 0.0] = 0.0 + + ip_data = data + + frame_number = data.size + last_value = 0.0 + for i in range(frame_number): + if data[i] <= 0.0: + j = i + 1 + for j in range(i + 1, frame_number): + if data[j] > 0.0: + break + if j < frame_number - 1: + if last_value > 0.0: + step = (data[j] - data[i - 1]) / float(j - i) + for k in range(i, j): + ip_data[k] = data[i - 1] + step * (k - i + 1) + else: + for k in range(i, j): + ip_data[k] = data[j] + else: + for k in range(i, frame_number): + ip_data[k] = last_value + else: + ip_data[i] = data[i] # 这里可能存在一个没有必要的拷贝 + last_value = data[i] + + return ip_data[:, 0], vuv_vector[:, 0] + + def resize_f0(self, x, target_len): + source = np.array(x) + source[source < 0.001] = np.nan + target = np.interp( + np.arange(0, len(source) * target_len, len(source)) / target_len, + np.arange(0, len(source)), + source, + ) + res = np.nan_to_num(target) + return res + + def compute_f0(self, wav, p_len=None): + if p_len is None: + p_len = wav.shape[0] // self.hop_length + f0, t = pyworld.dio( + wav.astype(np.double), + fs=self.sampling_rate, + f0_floor=self.f0_min, + f0_ceil=self.f0_max, + frame_period=1000 * self.hop_length / self.sampling_rate, + ) + f0 = pyworld.stonemask(wav.astype(np.double), f0, t, self.sampling_rate) + for index, pitch in enumerate(f0): + f0[index] = round(pitch, 1) + return self.interpolate_f0(self.resize_f0(f0, p_len))[0] + + def compute_f0_uv(self, wav, p_len=None): + if p_len is None: + p_len = wav.shape[0] // self.hop_length + f0, t = pyworld.dio( + wav.astype(np.double), + fs=self.sampling_rate, + f0_floor=self.f0_min, + f0_ceil=self.f0_max, + frame_period=1000 * self.hop_length / self.sampling_rate, + ) + f0 = pyworld.stonemask(wav.astype(np.double), f0, t, self.sampling_rate) + for index, pitch in enumerate(f0): + f0[index] = round(pitch, 1) + return self.interpolate_f0(self.resize_f0(f0, p_len)) diff --git a/system/tts_engines/rvc/lib/infer_pack/modules/F0Predictor/F0Predictor.py b/system/tts_engines/rvc/lib/infer_pack/modules/F0Predictor/F0Predictor.py new file mode 100644 index 0000000..384f43f --- /dev/null +++ b/system/tts_engines/rvc/lib/infer_pack/modules/F0Predictor/F0Predictor.py @@ -0,0 +1,6 @@ +class F0Predictor(object): + def compute_f0(self, wav, p_len): + pass + + def compute_f0_uv(self, wav, p_len): + pass diff --git a/system/tts_engines/rvc/lib/infer_pack/modules/F0Predictor/HarvestF0Predictor.py b/system/tts_engines/rvc/lib/infer_pack/modules/F0Predictor/HarvestF0Predictor.py new file mode 100644 index 0000000..e533f28 --- /dev/null +++ b/system/tts_engines/rvc/lib/infer_pack/modules/F0Predictor/HarvestF0Predictor.py @@ -0,0 +1,82 @@ +from infer_pack.modules.F0Predictor.F0Predictor import F0Predictor +import pyworld +import numpy as np + + +class HarvestF0Predictor(F0Predictor): + def __init__(self, hop_length=512, f0_min=50, f0_max=1100, sampling_rate=44100): + self.hop_length = hop_length + self.f0_min = f0_min + self.f0_max = f0_max + self.sampling_rate = sampling_rate + + def interpolate_f0(self, f0): + data = np.reshape(f0, (f0.size, 1)) + + vuv_vector = np.zeros((data.size, 1), dtype=np.float32) + vuv_vector[data > 0.0] = 1.0 + vuv_vector[data <= 0.0] = 0.0 + + ip_data = data + + frame_number = data.size + last_value = 0.0 + for i in range(frame_number): + if data[i] <= 0.0: + j = i + 1 + for j in range(i + 1, frame_number): + if data[j] > 0.0: + break + if j < frame_number - 1: + if last_value > 0.0: + step = (data[j] - data[i - 1]) / float(j - i) + for k in range(i, j): + ip_data[k] = data[i - 1] + step * (k - i + 1) + else: + for k in range(i, j): + ip_data[k] = data[j] + else: + for k in range(i, frame_number): + ip_data[k] = last_value + else: + ip_data[i] = data[i] + last_value = data[i] + + return ip_data[:, 0], vuv_vector[:, 0] + + def resize_f0(self, x, target_len): + source = np.array(x) + source[source < 0.001] = np.nan + target = np.interp( + np.arange(0, len(source) * target_len, len(source)) / target_len, + np.arange(0, len(source)), + source, + ) + res = np.nan_to_num(target) + return res + + def compute_f0(self, wav, p_len=None): + if p_len is None: + p_len = wav.shape[0] // self.hop_length + f0, t = pyworld.harvest( + wav.astype(np.double), + fs=self.sampling_rate, + f0_ceil=self.f0_max, + f0_floor=self.f0_min, + frame_period=1000 * self.hop_length / self.sampling_rate, + ) + f0 = pyworld.stonemask(wav.astype(np.double), f0, t, self.fs) + return self.interpolate_f0(self.resize_f0(f0, p_len))[0] + + def compute_f0_uv(self, wav, p_len=None): + if p_len is None: + p_len = wav.shape[0] // self.hop_length + f0, t = pyworld.harvest( + wav.astype(np.double), + fs=self.sampling_rate, + f0_floor=self.f0_min, + f0_ceil=self.f0_max, + frame_period=1000 * self.hop_length / self.sampling_rate, + ) + f0 = pyworld.stonemask(wav.astype(np.double), f0, t, self.sampling_rate) + return self.interpolate_f0(self.resize_f0(f0, p_len)) diff --git a/system/tts_engines/rvc/lib/infer_pack/modules/F0Predictor/PMF0Predictor.py b/system/tts_engines/rvc/lib/infer_pack/modules/F0Predictor/PMF0Predictor.py new file mode 100644 index 0000000..0fe1c74 --- /dev/null +++ b/system/tts_engines/rvc/lib/infer_pack/modules/F0Predictor/PMF0Predictor.py @@ -0,0 +1,93 @@ +from infer_pack.modules.F0Predictor.F0Predictor import F0Predictor +import parselmouth +import numpy as np + + +class PMF0Predictor(F0Predictor): + def __init__(self, hop_length=512, f0_min=50, f0_max=1100, sampling_rate=44100): + self.hop_length = hop_length + self.f0_min = f0_min + self.f0_max = f0_max + self.sampling_rate = sampling_rate + + def interpolate_f0(self, f0): + data = np.reshape(f0, (f0.size, 1)) + + vuv_vector = np.zeros((data.size, 1), dtype=np.float32) + vuv_vector[data > 0.0] = 1.0 + vuv_vector[data <= 0.0] = 0.0 + + ip_data = data + + frame_number = data.size + last_value = 0.0 + for i in range(frame_number): + if data[i] <= 0.0: + j = i + 1 + for j in range(i + 1, frame_number): + if data[j] > 0.0: + break + if j < frame_number - 1: + if last_value > 0.0: + step = (data[j] - data[i - 1]) / float(j - i) + for k in range(i, j): + ip_data[k] = data[i - 1] + step * (k - i + 1) + else: + for k in range(i, j): + ip_data[k] = data[j] + else: + for k in range(i, frame_number): + ip_data[k] = last_value + else: + ip_data[i] = data[i] # 这里可能存在一个没有必要的拷贝 + last_value = data[i] + + return ip_data[:, 0], vuv_vector[:, 0] + + def compute_f0(self, wav, p_len=None): + x = wav + if p_len is None: + p_len = x.shape[0] // self.hop_length + else: + assert abs(p_len - x.shape[0] // self.hop_length) < 4, "pad length error" + time_step = self.hop_length / self.sampling_rate * 1000 + f0 = ( + parselmouth.Sound(x, self.sampling_rate) + .to_pitch_ac( + time_step=time_step / 1000, + voicing_threshold=0.6, + pitch_floor=self.f0_min, + pitch_ceiling=self.f0_max, + ) + .selected_array["frequency"] + ) + + pad_size = (p_len - len(f0) + 1) // 2 + if pad_size > 0 or p_len - len(f0) - pad_size > 0: + f0 = np.pad(f0, [[pad_size, p_len - len(f0) - pad_size]], mode="constant") + f0, uv = self.interpolate_f0(f0) + return f0 + + def compute_f0_uv(self, wav, p_len=None): + x = wav + if p_len is None: + p_len = x.shape[0] // self.hop_length + else: + assert abs(p_len - x.shape[0] // self.hop_length) < 4, "pad length error" + time_step = self.hop_length / self.sampling_rate * 1000 + f0 = ( + parselmouth.Sound(x, self.sampling_rate) + .to_pitch_ac( + time_step=time_step / 1000, + voicing_threshold=0.6, + pitch_floor=self.f0_min, + pitch_ceiling=self.f0_max, + ) + .selected_array["frequency"] + ) + + pad_size = (p_len - len(f0) + 1) // 2 + if pad_size > 0 or p_len - len(f0) - pad_size > 0: + f0 = np.pad(f0, [[pad_size, p_len - len(f0) - pad_size]], mode="constant") + f0, uv = self.interpolate_f0(f0) + return f0, uv diff --git a/system/tts_engines/rvc/lib/infer_pack/modules/F0Predictor/__init__.py b/system/tts_engines/rvc/lib/infer_pack/modules/F0Predictor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/system/tts_engines/rvc/lib/infer_pack/transforms.py b/system/tts_engines/rvc/lib/infer_pack/transforms.py new file mode 100644 index 0000000..a11f799 --- /dev/null +++ b/system/tts_engines/rvc/lib/infer_pack/transforms.py @@ -0,0 +1,209 @@ +import torch +from torch.nn import functional as F + +import numpy as np + + +DEFAULT_MIN_BIN_WIDTH = 1e-3 +DEFAULT_MIN_BIN_HEIGHT = 1e-3 +DEFAULT_MIN_DERIVATIVE = 1e-3 + + +def piecewise_rational_quadratic_transform( + inputs, + unnormalized_widths, + unnormalized_heights, + unnormalized_derivatives, + inverse=False, + tails=None, + tail_bound=1.0, + min_bin_width=DEFAULT_MIN_BIN_WIDTH, + min_bin_height=DEFAULT_MIN_BIN_HEIGHT, + min_derivative=DEFAULT_MIN_DERIVATIVE, +): + if tails is None: + spline_fn = rational_quadratic_spline + spline_kwargs = {} + else: + spline_fn = unconstrained_rational_quadratic_spline + spline_kwargs = {"tails": tails, "tail_bound": tail_bound} + + outputs, logabsdet = spline_fn( + inputs=inputs, + unnormalized_widths=unnormalized_widths, + unnormalized_heights=unnormalized_heights, + unnormalized_derivatives=unnormalized_derivatives, + inverse=inverse, + min_bin_width=min_bin_width, + min_bin_height=min_bin_height, + min_derivative=min_derivative, + **spline_kwargs + ) + return outputs, logabsdet + + +def searchsorted(bin_locations, inputs, eps=1e-6): + bin_locations[..., -1] += eps + return torch.sum(inputs[..., None] >= bin_locations, dim=-1) - 1 + + +def unconstrained_rational_quadratic_spline( + inputs, + unnormalized_widths, + unnormalized_heights, + unnormalized_derivatives, + inverse=False, + tails="linear", + tail_bound=1.0, + min_bin_width=DEFAULT_MIN_BIN_WIDTH, + min_bin_height=DEFAULT_MIN_BIN_HEIGHT, + min_derivative=DEFAULT_MIN_DERIVATIVE, +): + inside_interval_mask = (inputs >= -tail_bound) & (inputs <= tail_bound) + outside_interval_mask = ~inside_interval_mask + + outputs = torch.zeros_like(inputs) + logabsdet = torch.zeros_like(inputs) + + if tails == "linear": + unnormalized_derivatives = F.pad(unnormalized_derivatives, pad=(1, 1)) + constant = np.log(np.exp(1 - min_derivative) - 1) + unnormalized_derivatives[..., 0] = constant + unnormalized_derivatives[..., -1] = constant + + outputs[outside_interval_mask] = inputs[outside_interval_mask] + logabsdet[outside_interval_mask] = 0 + else: + raise RuntimeError("{} tails are not implemented.".format(tails)) + + ( + outputs[inside_interval_mask], + logabsdet[inside_interval_mask], + ) = rational_quadratic_spline( + inputs=inputs[inside_interval_mask], + unnormalized_widths=unnormalized_widths[inside_interval_mask, :], + unnormalized_heights=unnormalized_heights[inside_interval_mask, :], + unnormalized_derivatives=unnormalized_derivatives[inside_interval_mask, :], + inverse=inverse, + left=-tail_bound, + right=tail_bound, + bottom=-tail_bound, + top=tail_bound, + min_bin_width=min_bin_width, + min_bin_height=min_bin_height, + min_derivative=min_derivative, + ) + + return outputs, logabsdet + + +def rational_quadratic_spline( + inputs, + unnormalized_widths, + unnormalized_heights, + unnormalized_derivatives, + inverse=False, + left=0.0, + right=1.0, + bottom=0.0, + top=1.0, + min_bin_width=DEFAULT_MIN_BIN_WIDTH, + min_bin_height=DEFAULT_MIN_BIN_HEIGHT, + min_derivative=DEFAULT_MIN_DERIVATIVE, +): + if torch.min(inputs) < left or torch.max(inputs) > right: + raise ValueError("Input to a transform is not within its domain") + + num_bins = unnormalized_widths.shape[-1] + + if min_bin_width * num_bins > 1.0: + raise ValueError("Minimal bin width too large for the number of bins") + if min_bin_height * num_bins > 1.0: + raise ValueError("Minimal bin height too large for the number of bins") + + widths = F.softmax(unnormalized_widths, dim=-1) + widths = min_bin_width + (1 - min_bin_width * num_bins) * widths + cumwidths = torch.cumsum(widths, dim=-1) + cumwidths = F.pad(cumwidths, pad=(1, 0), mode="constant", value=0.0) + cumwidths = (right - left) * cumwidths + left + cumwidths[..., 0] = left + cumwidths[..., -1] = right + widths = cumwidths[..., 1:] - cumwidths[..., :-1] + + derivatives = min_derivative + F.softplus(unnormalized_derivatives) + + heights = F.softmax(unnormalized_heights, dim=-1) + heights = min_bin_height + (1 - min_bin_height * num_bins) * heights + cumheights = torch.cumsum(heights, dim=-1) + cumheights = F.pad(cumheights, pad=(1, 0), mode="constant", value=0.0) + cumheights = (top - bottom) * cumheights + bottom + cumheights[..., 0] = bottom + cumheights[..., -1] = top + heights = cumheights[..., 1:] - cumheights[..., :-1] + + if inverse: + bin_idx = searchsorted(cumheights, inputs)[..., None] + else: + bin_idx = searchsorted(cumwidths, inputs)[..., None] + + input_cumwidths = cumwidths.gather(-1, bin_idx)[..., 0] + input_bin_widths = widths.gather(-1, bin_idx)[..., 0] + + input_cumheights = cumheights.gather(-1, bin_idx)[..., 0] + delta = heights / widths + input_delta = delta.gather(-1, bin_idx)[..., 0] + + input_derivatives = derivatives.gather(-1, bin_idx)[..., 0] + input_derivatives_plus_one = derivatives[..., 1:].gather(-1, bin_idx)[..., 0] + + input_heights = heights.gather(-1, bin_idx)[..., 0] + + if inverse: + a = (inputs - input_cumheights) * ( + input_derivatives + input_derivatives_plus_one - 2 * input_delta + ) + input_heights * (input_delta - input_derivatives) + b = input_heights * input_derivatives - (inputs - input_cumheights) * ( + input_derivatives + input_derivatives_plus_one - 2 * input_delta + ) + c = -input_delta * (inputs - input_cumheights) + + discriminant = b.pow(2) - 4 * a * c + assert (discriminant >= 0).all() + + root = (2 * c) / (-b - torch.sqrt(discriminant)) + outputs = root * input_bin_widths + input_cumwidths + + theta_one_minus_theta = root * (1 - root) + denominator = input_delta + ( + (input_derivatives + input_derivatives_plus_one - 2 * input_delta) + * theta_one_minus_theta + ) + derivative_numerator = input_delta.pow(2) * ( + input_derivatives_plus_one * root.pow(2) + + 2 * input_delta * theta_one_minus_theta + + input_derivatives * (1 - root).pow(2) + ) + logabsdet = torch.log(derivative_numerator) - 2 * torch.log(denominator) + + return outputs, -logabsdet + else: + theta = (inputs - input_cumwidths) / input_bin_widths + theta_one_minus_theta = theta * (1 - theta) + + numerator = input_heights * ( + input_delta * theta.pow(2) + input_derivatives * theta_one_minus_theta + ) + denominator = input_delta + ( + (input_derivatives + input_derivatives_plus_one - 2 * input_delta) + * theta_one_minus_theta + ) + outputs = input_cumheights + numerator / denominator + + derivative_numerator = input_delta.pow(2) * ( + input_derivatives_plus_one * theta.pow(2) + + 2 * input_delta * theta_one_minus_theta + + input_derivatives * (1 - theta).pow(2) + ) + logabsdet = torch.log(derivative_numerator) - 2 * torch.log(denominator) + + return outputs, logabsdet diff --git a/system/tts_engines/rvc/lib/rmvpe.py b/system/tts_engines/rvc/lib/rmvpe.py new file mode 100644 index 0000000..35b4557 --- /dev/null +++ b/system/tts_engines/rvc/lib/rmvpe.py @@ -0,0 +1,390 @@ +import torch.nn as nn +import torch, numpy as np +import torch.nn.functional as F +from librosa.filters import mel + + +class BiGRU(nn.Module): + def __init__(self, input_features, hidden_features, num_layers): + super(BiGRU, self).__init__() + self.gru = nn.GRU( + input_features, + hidden_features, + num_layers=num_layers, + batch_first=True, + bidirectional=True, + ) + + def forward(self, x): + return self.gru(x)[0] + + +class ConvBlockRes(nn.Module): + def __init__(self, in_channels, out_channels, momentum=0.01): + super(ConvBlockRes, self).__init__() + self.conv = nn.Sequential( + nn.Conv2d( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=(3, 3), + stride=(1, 1), + padding=(1, 1), + bias=False, + ), + nn.BatchNorm2d(out_channels, momentum=momentum), + nn.ReLU(), + nn.Conv2d( + in_channels=out_channels, + out_channels=out_channels, + kernel_size=(3, 3), + stride=(1, 1), + padding=(1, 1), + bias=False, + ), + nn.BatchNorm2d(out_channels, momentum=momentum), + nn.ReLU(), + ) + if in_channels != out_channels: + self.shortcut = nn.Conv2d(in_channels, out_channels, (1, 1)) + self.is_shortcut = True + else: + self.is_shortcut = False + + def forward(self, x): + if self.is_shortcut: + return self.conv(x) + self.shortcut(x) + else: + return self.conv(x) + x + + +class Encoder(nn.Module): + def __init__( + self, + in_channels, + in_size, + n_encoders, + kernel_size, + n_blocks, + out_channels=16, + momentum=0.01, + ): + super(Encoder, self).__init__() + self.n_encoders = n_encoders + self.bn = nn.BatchNorm2d(in_channels, momentum=momentum) + self.layers = nn.ModuleList() + self.latent_channels = [] + for i in range(self.n_encoders): + self.layers.append( + ResEncoderBlock( + in_channels, out_channels, kernel_size, n_blocks, momentum=momentum + ) + ) + self.latent_channels.append([out_channels, in_size]) + in_channels = out_channels + out_channels *= 2 + in_size //= 2 + self.out_size = in_size + self.out_channel = out_channels + + def forward(self, x): + concat_tensors = [] + x = self.bn(x) + for i in range(self.n_encoders): + _, x = self.layers[i](x) + concat_tensors.append(_) + return x, concat_tensors + + +class ResEncoderBlock(nn.Module): + def __init__( + self, in_channels, out_channels, kernel_size, n_blocks=1, momentum=0.01 + ): + super(ResEncoderBlock, self).__init__() + self.n_blocks = n_blocks + self.conv = nn.ModuleList() + self.conv.append(ConvBlockRes(in_channels, out_channels, momentum)) + for i in range(n_blocks - 1): + self.conv.append(ConvBlockRes(out_channels, out_channels, momentum)) + self.kernel_size = kernel_size + if self.kernel_size is not None: + self.pool = nn.AvgPool2d(kernel_size=kernel_size) + + def forward(self, x): + for i in range(self.n_blocks): + x = self.conv[i](x) + if self.kernel_size is not None: + return x, self.pool(x) + else: + return x + + +class Intermediate(nn.Module): # + def __init__(self, in_channels, out_channels, n_inters, n_blocks, momentum=0.01): + super(Intermediate, self).__init__() + self.n_inters = n_inters + self.layers = nn.ModuleList() + self.layers.append( + ResEncoderBlock(in_channels, out_channels, None, n_blocks, momentum) + ) + for i in range(self.n_inters - 1): + self.layers.append( + ResEncoderBlock(out_channels, out_channels, None, n_blocks, momentum) + ) + + def forward(self, x): + for i in range(self.n_inters): + x = self.layers[i](x) + return x + + +class ResDecoderBlock(nn.Module): + def __init__(self, in_channels, out_channels, stride, n_blocks=1, momentum=0.01): + super(ResDecoderBlock, self).__init__() + out_padding = (0, 1) if stride == (1, 2) else (1, 1) + self.n_blocks = n_blocks + self.conv1 = nn.Sequential( + nn.ConvTranspose2d( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=(3, 3), + stride=stride, + padding=(1, 1), + output_padding=out_padding, + bias=False, + ), + nn.BatchNorm2d(out_channels, momentum=momentum), + nn.ReLU(), + ) + self.conv2 = nn.ModuleList() + self.conv2.append(ConvBlockRes(out_channels * 2, out_channels, momentum)) + for i in range(n_blocks - 1): + self.conv2.append(ConvBlockRes(out_channels, out_channels, momentum)) + + def forward(self, x, concat_tensor): + x = self.conv1(x) + x = torch.cat((x, concat_tensor), dim=1) + for i in range(self.n_blocks): + x = self.conv2[i](x) + return x + + +class Decoder(nn.Module): + def __init__(self, in_channels, n_decoders, stride, n_blocks, momentum=0.01): + super(Decoder, self).__init__() + self.layers = nn.ModuleList() + self.n_decoders = n_decoders + for i in range(self.n_decoders): + out_channels = in_channels // 2 + self.layers.append( + ResDecoderBlock(in_channels, out_channels, stride, n_blocks, momentum) + ) + in_channels = out_channels + + def forward(self, x, concat_tensors): + for i in range(self.n_decoders): + x = self.layers[i](x, concat_tensors[-1 - i]) + return x + + +class DeepUnet(nn.Module): + def __init__( + self, + kernel_size, + n_blocks, + en_de_layers=5, + inter_layers=4, + in_channels=1, + en_out_channels=16, + ): + super(DeepUnet, self).__init__() + self.encoder = Encoder( + in_channels, 128, en_de_layers, kernel_size, n_blocks, en_out_channels + ) + self.intermediate = Intermediate( + self.encoder.out_channel // 2, + self.encoder.out_channel, + inter_layers, + n_blocks, + ) + self.decoder = Decoder( + self.encoder.out_channel, en_de_layers, kernel_size, n_blocks + ) + + def forward(self, x): + x, concat_tensors = self.encoder(x) + x = self.intermediate(x) + x = self.decoder(x, concat_tensors) + return x + + +class E2E(nn.Module): + def __init__( + self, + n_blocks, + n_gru, + kernel_size, + en_de_layers=5, + inter_layers=4, + in_channels=1, + en_out_channels=16, + ): + super(E2E, self).__init__() + self.unet = DeepUnet( + kernel_size, + n_blocks, + en_de_layers, + inter_layers, + in_channels, + en_out_channels, + ) + self.cnn = nn.Conv2d(en_out_channels, 3, (3, 3), padding=(1, 1)) + if n_gru: + self.fc = nn.Sequential( + BiGRU(3 * 128, 256, n_gru), + nn.Linear(512, 360), + nn.Dropout(0.25), + nn.Sigmoid(), + ) + + def forward(self, mel): + mel = mel.transpose(-1, -2).unsqueeze(1) + x = self.cnn(self.unet(mel)).transpose(1, 2).flatten(-2) + x = self.fc(x) + return x + + +class MelSpectrogram(torch.nn.Module): + def __init__( + self, + is_half, + n_mel_channels, + sampling_rate, + win_length, + hop_length, + n_fft=None, + mel_fmin=0, + mel_fmax=None, + clamp=1e-5, + ): + super().__init__() + n_fft = win_length if n_fft is None else n_fft + self.hann_window = {} + mel_basis = mel( + sr=sampling_rate, + n_fft=n_fft, + n_mels=n_mel_channels, + fmin=mel_fmin, + fmax=mel_fmax, + htk=True, + ) + mel_basis = torch.from_numpy(mel_basis).float() + self.register_buffer("mel_basis", mel_basis) + self.n_fft = win_length if n_fft is None else n_fft + self.hop_length = hop_length + self.win_length = win_length + self.sampling_rate = sampling_rate + self.n_mel_channels = n_mel_channels + self.clamp = clamp + self.is_half = is_half + + def forward(self, audio, keyshift=0, speed=1, center=True): + factor = 2 ** (keyshift / 12) + n_fft_new = int(np.round(self.n_fft * factor)) + win_length_new = int(np.round(self.win_length * factor)) + hop_length_new = int(np.round(self.hop_length * speed)) + keyshift_key = str(keyshift) + "_" + str(audio.device) + if keyshift_key not in self.hann_window: + self.hann_window[keyshift_key] = torch.hann_window(win_length_new).to( + audio.device + ) + fft = torch.stft( + audio, + n_fft=n_fft_new, + hop_length=hop_length_new, + win_length=win_length_new, + window=self.hann_window[keyshift_key], + center=center, + return_complex=True, + ) + magnitude = torch.sqrt(fft.real.pow(2) + fft.imag.pow(2)) + if keyshift != 0: + size = self.n_fft // 2 + 1 + resize = magnitude.size(1) + if resize < size: + magnitude = F.pad(magnitude, (0, 0, 0, size - resize)) + magnitude = magnitude[:, :size, :] * self.win_length / win_length_new + mel_output = torch.matmul(self.mel_basis, magnitude) + if self.is_half == True: + mel_output = mel_output.half() + log_mel_spec = torch.log(torch.clamp(mel_output, min=self.clamp)) + return log_mel_spec + + +class RMVPE: + def __init__(self, model_path, is_half, device=None): + #print("RMVPE - model_path is:", model_path) + #print("RMVPE - is_half is:", is_half) + self.resample_kernel = {} + model = E2E(4, 1, (2, 2)) + ckpt = torch.load(model_path, map_location="cpu") + model.load_state_dict(ckpt) + model.eval() + if is_half == True: + model = model.half() + self.model = model + self.resample_kernel = {} + self.is_half = is_half + if device is None: + device = "cuda" if torch.cuda.is_available() else "cpu" + self.device = device + self.mel_extractor = MelSpectrogram( + is_half, 128, 16000, 1024, 160, None, 30, 8000 + ).to(device) + self.model = self.model.to(device) + cents_mapping = 20 * np.arange(360) + 1997.3794084376191 + self.cents_mapping = np.pad(cents_mapping, (4, 4)) # 368 + + def mel2hidden(self, mel): + with torch.no_grad(): + n_frames = mel.shape[-1] + mel = F.pad( + mel, (0, 32 * ((n_frames - 1) // 32 + 1) - n_frames), mode="reflect" + ) + hidden = self.model(mel) + return hidden[:, :n_frames] + + def decode(self, hidden, thred=0.03): + cents_pred = self.to_local_average_cents(hidden, thred=thred) + f0 = 10 * (2 ** (cents_pred / 1200)) + f0[f0 == 10] = 0 + return f0 + + def infer_from_audio(self, audio, thred=0.03): + audio = torch.from_numpy(audio).float().to(self.device).unsqueeze(0) + mel = self.mel_extractor(audio, center=True) + hidden = self.mel2hidden(mel) + hidden = hidden.squeeze(0).cpu().numpy() + if self.is_half == True: + hidden = hidden.astype("float32") + f0 = self.decode(hidden, thred=thred) + return f0 + + def to_local_average_cents(self, salience, thred=0.05): + center = np.argmax(salience, axis=1) + salience = np.pad(salience, ((0, 0), (4, 4))) + center += 4 + todo_salience = [] + todo_cents_mapping = [] + starts = center - 4 + ends = center + 5 + for idx in range(salience.shape[0]): + todo_salience.append(salience[:, starts[idx] : ends[idx]][idx]) + todo_cents_mapping.append(self.cents_mapping[starts[idx] : ends[idx]]) + todo_salience = np.array(todo_salience) + todo_cents_mapping = np.array(todo_cents_mapping) + product_sum = np.sum(todo_salience * todo_cents_mapping, 1) + weight_sum = np.sum(todo_salience, 1) + devided = product_sum / weight_sum + maxx = np.max(salience, axis=1) + devided[maxx <= thred] = 0 + return devided diff --git a/system/tts_engines/rvc/lib/tools/analyzer.py b/system/tts_engines/rvc/lib/tools/analyzer.py new file mode 100644 index 0000000..f4b7943 --- /dev/null +++ b/system/tts_engines/rvc/lib/tools/analyzer.py @@ -0,0 +1,76 @@ +import numpy as np +import matplotlib.pyplot as plt +import librosa.display +import librosa + + +def calculate_features(y, sr): + stft = np.abs(librosa.stft(y)) + duration = librosa.get_duration(y=y, sr=sr) + cent = librosa.feature.spectral_centroid(S=stft, sr=sr)[0] + bw = librosa.feature.spectral_bandwidth(S=stft, sr=sr)[0] + rolloff = librosa.feature.spectral_rolloff(S=stft, sr=sr)[0] + return stft, duration, cent, bw, rolloff + + +def plot_title(title): + plt.suptitle(title, fontsize=16, fontweight="bold") + + +def plot_spectrogram(y, sr, stft, duration, cmap="inferno"): + plt.subplot(3, 1, 1) + plt.imshow( + librosa.amplitude_to_db(stft, ref=np.max), + origin="lower", + extent=[0, duration, 0, sr / 1000], + aspect="auto", + cmap=cmap, # Change the colormap here + ) + plt.colorbar(format="%+2.0f dB") + plt.xlabel("Time (s)") + plt.ylabel("Frequency (kHz)") + plt.title("Spectrogram") + + +def plot_waveform(y, sr, duration): + plt.subplot(3, 1, 2) + librosa.display.waveshow(y, sr=sr) + plt.xlabel("Time (s)") + plt.ylabel("Amplitude") + plt.title("Waveform") + + +def plot_features(times, cent, bw, rolloff, duration): + plt.subplot(3, 1, 3) + plt.plot(times, cent, label="Spectral Centroid (kHz)", color="b") + plt.plot(times, bw, label="Spectral Bandwidth (kHz)", color="g") + plt.plot(times, rolloff, label="Spectral Rolloff (kHz)", color="r") + plt.xlabel("Time (s)") + plt.title("Spectral Features") + plt.legend() + + +def analyze_audio(audio_file, save_plot_path="logs/audio_analysis.png"): + y, sr = librosa.load(audio_file) + stft, duration, cent, bw, rolloff = calculate_features(y, sr) + + plt.figure(figsize=(12, 10)) + + plot_title("Audio Analysis" + " - " + audio_file.split("/")[-1]) + plot_spectrogram(y, sr, stft, duration) + plot_waveform(y, sr, duration) + plot_features(librosa.times_like(cent), cent, bw, rolloff, duration) + + plt.tight_layout() + + if save_plot_path: + plt.savefig(save_plot_path, bbox_inches="tight", dpi=300) + plt.close() + + audio_info = f"""Sample Rate: {sr}\nDuration: {( + str(round(duration, 2)) + " seconds" + if duration < 60 + else str(round(duration / 60, 2)) + " minutes" + )}\nNumber of Samples: {len(y)}\nBits per Sample: {librosa.get_samplerate(audio_file)}\nChannels: {"Mono (1)" if y.ndim == 1 else "Stereo (2)"}""" + + return audio_info, save_plot_path diff --git a/system/tts_engines/rvc/lib/tools/gdown.py b/system/tts_engines/rvc/lib/tools/gdown.py new file mode 100644 index 0000000..a9c526b --- /dev/null +++ b/system/tts_engines/rvc/lib/tools/gdown.py @@ -0,0 +1,409 @@ +from __future__ import print_function + +import json +import os +import os.path as osp +import re +import warnings +from six.moves import urllib_parse +import shutil +import sys +import tempfile +import textwrap +import time + +import requests +import six +import tqdm + + +def indent(text, prefix): + def prefixed_lines(): + for line in text.splitlines(True): + yield (prefix + line if line.strip() else line) + + return "".join(prefixed_lines()) + + +class FileURLRetrievalError(Exception): + pass + + +class FolderContentsMaximumLimitError(Exception): + pass + + +def parse_url(url, warning=True): + """Parse URLs especially for Google Drive links. + + file_id: ID of file on Google Drive. + is_download_link: Flag if it is download link of Google Drive. + """ + parsed = urllib_parse.urlparse(url) + query = urllib_parse.parse_qs(parsed.query) + is_gdrive = parsed.hostname in ["drive.google.com", "docs.google.com"] + is_download_link = parsed.path.endswith("/uc") + + if not is_gdrive: + return is_gdrive, is_download_link + + file_id = None + if "id" in query: + file_ids = query["id"] + if len(file_ids) == 1: + file_id = file_ids[0] + else: + patterns = [ + r"^/file/d/(.*?)/(edit|view)$", + r"^/file/u/[0-9]+/d/(.*?)/(edit|view)$", + r"^/document/d/(.*?)/(edit|htmlview|view)$", + r"^/document/u/[0-9]+/d/(.*?)/(edit|htmlview|view)$", + r"^/presentation/d/(.*?)/(edit|htmlview|view)$", + r"^/presentation/u/[0-9]+/d/(.*?)/(edit|htmlview|view)$", + r"^/spreadsheets/d/(.*?)/(edit|htmlview|view)$", + r"^/spreadsheets/u/[0-9]+/d/(.*?)/(edit|htmlview|view)$", + ] + for pattern in patterns: + match = re.match(pattern, parsed.path) + if match: + file_id = match.groups()[0] + break + + if warning and not is_download_link: + warnings.warn( + "You specified a Google Drive link that is not the correct link " + "to download a file. You might want to try `--fuzzy` option " + "or the following url: {url}".format( + url="https://drive.google.com/uc?id={}".format(file_id) + ) + ) + + return file_id, is_download_link + + +CHUNK_SIZE = 512 * 1024 # 512KB +home = osp.expanduser("~") + + +def get_url_from_gdrive_confirmation(contents): + url = "" + m = re.search(r'href="(\/uc\?export=download[^"]+)', contents) + if m: + url = "https://docs.google.com" + m.groups()[0] + url = url.replace("&", "&") + return url + + m = re.search(r'href="/open\?id=([^"]+)"', contents) + if m: + url = m.groups()[0] + uuid = re.search( + r'(.*)

', contents) + if m: + error = m.groups()[0] + raise FileURLRetrievalError(error) + + raise FileURLRetrievalError( + "Cannot retrieve the public link of the file. " + "You may need to change the permission to " + "'Anyone with the link', or have had many accesses." + ) + + +def _get_session(proxy, use_cookies, return_cookies_file=False): + sess = requests.session() + + sess.headers.update( + {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6)"} + ) + + if proxy is not None: + sess.proxies = {"http": proxy, "https": proxy} + print("Using proxy:", proxy, file=sys.stderr) + + # Load cookies if exists + cookies_file = osp.join(home, ".cache/gdown/cookies.json") + if osp.exists(cookies_file) and use_cookies: + with open(cookies_file) as f: + cookies = json.load(f) + for k, v in cookies: + sess.cookies[k] = v + + if return_cookies_file: + return sess, cookies_file + else: + return sess + + +def download( + url=None, + output=None, + quiet=False, + proxy=None, + speed=None, + use_cookies=True, + verify=True, + id=None, + fuzzy=True, + resume=False, + format=None, +): + """Download file from URL. + + Parameters + ---------- + url: str + URL. Google Drive URL is also supported. + output: str + Output filename. Default is basename of URL. + quiet: bool + Suppress terminal output. Default is False. + proxy: str + Proxy. + speed: float + Download byte size per second (e.g., 256KB/s = 256 * 1024). + use_cookies: bool + Flag to use cookies. Default is True. + verify: bool or string + Either a bool, in which case it controls whether the server's TLS + certificate is verified, or a string, in which case it must be a path + to a CA bundle to use. Default is True. + id: str + Google Drive's file ID. + fuzzy: bool + Fuzzy extraction of Google Drive's file Id. Default is False. + resume: bool + Resume the download from existing tmp file if possible. + Default is False. + format: str, optional + Format of Google Docs, Spreadsheets and Slides. Default is: + - Google Docs: 'docx' + - Google Spreadsheet: 'xlsx' + - Google Slides: 'pptx' + + Returns + ------- + output: str + Output filename. + """ + if not (id is None) ^ (url is None): + raise ValueError("Either url or id has to be specified") + if id is not None: + url = "https://drive.google.com/uc?id={id}".format(id=id) + + url_origin = url + + sess, cookies_file = _get_session( + proxy=proxy, use_cookies=use_cookies, return_cookies_file=True + ) + + gdrive_file_id, is_gdrive_download_link = parse_url(url, warning=not fuzzy) + + if fuzzy and gdrive_file_id: + # overwrite the url with fuzzy match of a file id + url = "https://drive.google.com/uc?id={id}".format(id=gdrive_file_id) + url_origin = url + is_gdrive_download_link = True + + while True: + res = sess.get(url, stream=True, verify=verify) + + if url == url_origin and res.status_code == 500: + # The file could be Google Docs or Spreadsheets. + url = "https://drive.google.com/open?id={id}".format(id=gdrive_file_id) + continue + + if res.headers["Content-Type"].startswith("text/html"): + m = re.search("(.+)", res.text) + if m and m.groups()[0].endswith(" - Google Docs"): + url = ( + "https://docs.google.com/document/d/{id}/export" + "?format={format}".format( + id=gdrive_file_id, + format="docx" if format is None else format, + ) + ) + continue + elif m and m.groups()[0].endswith(" - Google Sheets"): + url = ( + "https://docs.google.com/spreadsheets/d/{id}/export" + "?format={format}".format( + id=gdrive_file_id, + format="xlsx" if format is None else format, + ) + ) + continue + elif m and m.groups()[0].endswith(" - Google Slides"): + url = ( + "https://docs.google.com/presentation/d/{id}/export" + "?format={format}".format( + id=gdrive_file_id, + format="pptx" if format is None else format, + ) + ) + continue + elif ( + "Content-Disposition" in res.headers + and res.headers["Content-Disposition"].endswith("pptx") + and format not in {None, "pptx"} + ): + url = ( + "https://docs.google.com/presentation/d/{id}/export" + "?format={format}".format( + id=gdrive_file_id, + format="pptx" if format is None else format, + ) + ) + continue + + if use_cookies: + if not osp.exists(osp.dirname(cookies_file)): + os.makedirs(osp.dirname(cookies_file)) + # Save cookies + with open(cookies_file, "w") as f: + cookies = [ + (k, v) + for k, v in sess.cookies.items() + if not k.startswith("download_warning_") + ] + json.dump(cookies, f, indent=2) + + if "Content-Disposition" in res.headers: + # This is the file + break + if not (gdrive_file_id and is_gdrive_download_link): + break + + # Need to redirect with confirmation + try: + url = get_url_from_gdrive_confirmation(res.text) + except FileURLRetrievalError as e: + message = ( + "Failed to retrieve file url:\n\n{}\n\n" + "You may still be able to access the file from the browser:" + "\n\n\t{}\n\n" + "but Gdown can't. Please check connections and permissions." + ).format( + indent("\n".join(textwrap.wrap(str(e))), prefix="\t"), + url_origin, + ) + raise FileURLRetrievalError(message) + + if gdrive_file_id and is_gdrive_download_link: + content_disposition = six.moves.urllib_parse.unquote( + res.headers["Content-Disposition"] + ) + + m = re.search(r"filename\*=UTF-8''(.*)", content_disposition) + if not m: + m = re.search(r'filename=["\']?(.*?)["\']?$', content_disposition) + filename_from_url = m.groups()[0] + filename_from_url = filename_from_url.replace(osp.sep, "_") + else: + filename_from_url = osp.basename(url) + + if output is None: + output = filename_from_url + + output_is_path = isinstance(output, six.string_types) + if output_is_path and output.endswith(osp.sep): + if not osp.exists(output): + os.makedirs(output) + output = osp.join(output, filename_from_url) + + if output_is_path: + existing_tmp_files = [] + for file in os.listdir(osp.dirname(output) or "."): + if file.startswith(osp.basename(output)): + existing_tmp_files.append(osp.join(osp.dirname(output), file)) + if resume and existing_tmp_files: + if len(existing_tmp_files) != 1: + print( + "There are multiple temporary files to resume:", + file=sys.stderr, + ) + print("\n") + for file in existing_tmp_files: + print("\t", file, file=sys.stderr) + print("\n") + print( + "Please remove them except one to resume downloading.", + file=sys.stderr, + ) + return + tmp_file = existing_tmp_files[0] + else: + resume = False + # mkstemp is preferred, but does not work on Windows + # https://github.com/wkentaro/gdown/issues/153 + tmp_file = tempfile.mktemp( + suffix=tempfile.template, + prefix=osp.basename(output), + dir=osp.dirname(output), + ) + f = open(tmp_file, "ab") + else: + tmp_file = None + f = output + + if tmp_file is not None and f.tell() != 0: + headers = {"Range": "bytes={}-".format(f.tell())} + res = sess.get(url, headers=headers, stream=True, verify=verify) + + if not quiet: + # print("Downloading...", file=sys.stderr) + if resume: + print("Resume:", tmp_file, file=sys.stderr) + # if url_origin != url: + # print("From (original):", url_origin, file=sys.stderr) + # print("From (redirected):", url, file=sys.stderr) + # else: + # print("From:", url, file=sys.stderr) + print( + "To:", + osp.abspath(output) if output_is_path else output, + file=sys.stderr, + ) + + try: + total = res.headers.get("Content-Length") + if total is not None: + total = int(total) + if not quiet: + pbar = tqdm.tqdm(total=total, unit="B", unit_scale=True) + t_start = time.time() + for chunk in res.iter_content(chunk_size=CHUNK_SIZE): + f.write(chunk) + if not quiet: + pbar.update(len(chunk)) + if speed is not None: + elapsed_time_expected = 1.0 * pbar.n / speed + elapsed_time = time.time() - t_start + if elapsed_time < elapsed_time_expected: + time.sleep(elapsed_time_expected - elapsed_time) + if not quiet: + pbar.close() + if tmp_file: + f.close() + shutil.move(tmp_file, output) + finally: + sess.close() + + return output diff --git a/system/tts_engines/rvc/lib/tools/launch_tensorboard.py b/system/tts_engines/rvc/lib/tools/launch_tensorboard.py new file mode 100644 index 0000000..bae2219 --- /dev/null +++ b/system/tts_engines/rvc/lib/tools/launch_tensorboard.py @@ -0,0 +1,16 @@ +import time +from tensorboard import program + +log_path = "logs" + + +def launch_tensorboard_pipeline(): + tb = program.TensorBoard() + tb.configure(argv=[None, "--logdir", log_path]) + url = tb.launch() + print( + f"Access the tensorboard using the following link:\n{url}?pinnedCards=%5B%7B%22plugin%22%3A%22scalars%22%2C%22tag%22%3A%22loss%2Fg%2Ftotal%22%7D%2C%7B%22plugin%22%3A%22scalars%22%2C%22tag%22%3A%22loss%2Fd%2Ftotal%22%7D%2C%7B%22plugin%22%3A%22scalars%22%2C%22tag%22%3A%22loss%2Fg%2Fkl%22%7D%2C%7B%22plugin%22%3A%22scalars%22%2C%22tag%22%3A%22loss%2Fg%2Fmel%22%7D%5D" + ) + + while True: + time.sleep(600) diff --git a/system/tts_engines/rvc/lib/tools/model_download.py b/system/tts_engines/rvc/lib/tools/model_download.py new file mode 100644 index 0000000..9df3606 --- /dev/null +++ b/system/tts_engines/rvc/lib/tools/model_download.py @@ -0,0 +1,353 @@ +import os +import sys +import wget +import zipfile +from bs4 import BeautifulSoup +import requests +from urllib.parse import unquote, urlencode, parse_qs, urlparse +import re +import shutil +import six + + +def find_folder_parent(search_dir, folder_name): + for dirpath, dirnames, _ in os.walk(search_dir): + if folder_name in dirnames: + return os.path.abspath(dirpath) + return None + + +now_dir = os.getcwd() +sys.path.append(now_dir) + +from rvc.lib.utils import format_title + +from rvc.lib.tools import gdown + +file_path = find_folder_parent(now_dir, "logs") + +zips_path = os.getcwd() + "/logs/zips" + + +def search_pth_index(folder): + pth_paths = [ + os.path.join(folder, file) + for file in os.listdir(folder) + if os.path.isfile(os.path.join(folder, file)) and file.endswith(".pth") + ] + index_paths = [ + os.path.join(folder, file) + for file in os.listdir(folder) + if os.path.isfile(os.path.join(folder, file)) and file.endswith(".index") + ] + + return pth_paths, index_paths + + +def get_mediafire_download_link(url): + response = requests.get(url) + response.raise_for_status() + soup = BeautifulSoup(response.text, "html.parser") + download_button = soup.find( + "a", {"class": "input popsok", "aria-label": "Download file"} + ) + if download_button: + download_link = download_button.get("href") + return download_link + else: + return None + + +def download_from_url(url): + os.makedirs(zips_path, exist_ok=True) + if url != "": + if "drive.google.com" in url: + if "file/d/" in url: + file_id = url.split("file/d/")[1].split("/")[0] + elif "id=" in url: + file_id = url.split("id=")[1].split("&")[0] + else: + return None + + if file_id: + os.chdir(zips_path) + try: + gdown.download( + f"https://drive.google.com/uc?id={file_id}", + quiet=True, + fuzzy=True, + ) + except Exception as error: + error_message = str(error) + if ( + "Too many users have viewed or downloaded this file recently" + in error_message + ): + os.chdir(now_dir) + return "too much use" + elif ( + "Cannot retrieve the public link of the file." in error_message + ): + os.chdir(now_dir) + return "private link" + else: + print(error_message) + os.chdir(now_dir) + return None + elif "disk.yandex.ru" in url: + base_url = "https://cloud-api.yandex.net/v1/disk/public/resources/download?" + public_key = url + final_url = base_url + urlencode(dict(public_key=public_key)) + response = requests.get(final_url) + download_url = response.json()["href"] + download_response = requests.get(download_url) + + if download_response.status_code == 200: + filename = parse_qs(urlparse(unquote(download_url)).query).get( + "filename", [""] + )[0] + if filename: + os.chdir(zips_path) + with open(filename, "wb") as f: + f.write(download_response.content) + else: + print("Failed to get filename from URL.") + return None + + elif "pixeldrain.com" in url: + try: + file_id = url.split("pixeldrain.com/u/")[1] + os.chdir(zips_path) + print(file_id) + response = requests.get(f"https://pixeldrain.com/api/file/{file_id}") + if response.status_code == 200: + file_name = ( + response.headers.get("Content-Disposition") + .split("filename=")[-1] + .strip('";') + ) + os.makedirs(zips_path, exist_ok=True) + with open(os.path.join(zips_path, file_name), "wb") as newfile: + newfile.write(response.content) + os.chdir(file_path) + return "downloaded" + else: + os.chdir(file_path) + return None + except Exception as e: + print(e) + os.chdir(file_path) + return None + + elif "cdn.discordapp.com" in url: + file = requests.get(url) + os.chdir(zips_path) + if file.status_code == 200: + name = url.split("/") + with open(os.path.join(name[-1]), "wb") as newfile: + newfile.write(file.content) + else: + return None + elif "/blob/" in url or "/resolve/" in url: + os.chdir(zips_path) + if "/blob/" in url: + url = url.replace("/blob/", "/resolve/") + + response = requests.get(url, stream=True) + if response.status_code == 200: + content_disposition = six.moves.urllib_parse.unquote( + response.headers["Content-Disposition"] + ) + m = re.search(r'filename="([^"]+)"', content_disposition) + file_name = m.groups()[0] + file_name = file_name.replace(os.path.sep, "_") + total_size_in_bytes = int(response.headers.get("content-length", 0)) + block_size = 1024 + progress_bar_length = 50 + progress = 0 + + with open(os.path.join(zips_path, file_name), "wb") as file: + for data in response.iter_content(block_size): + file.write(data) + progress += len(data) + progress_percent = int((progress / total_size_in_bytes) * 100) + num_dots = int( + (progress / total_size_in_bytes) * progress_bar_length + ) + progress_bar = ( + "[" + + "." * num_dots + + " " * (progress_bar_length - num_dots) + + "]" + ) + print( + f"{progress_percent}% {progress_bar} {progress}/{total_size_in_bytes} ", + end="\r", + ) + if progress_percent == 100: + print("\n") + + else: + os.chdir(now_dir) + return None + elif "/tree/main" in url: + os.chdir(zips_path) + response = requests.get(url) + soup = BeautifulSoup(response.content, "html.parser") + temp_url = "" + for link in soup.find_all("a", href=True): + if link["href"].endswith(".zip"): + temp_url = link["href"] + break + if temp_url: + url = temp_url + url = url.replace("blob", "resolve") + if "huggingface.co" not in url: + url = "https://huggingface.co" + url + + wget.download(url) + else: + os.chdir(now_dir) + return None + + params = {"id": f"eq.{id_number}"} + response = requests.get(url, headers=headers, params=params) + if response.status_code == 200: + json_response = response.json() + print(json_response) + if json_response: + link = json_response[0]["link"] + verify = download_from_url(link) + if verify == "downloaded": + return "downloaded" + else: + return None + else: + return None + else: + try: + os.chdir(zips_path) + wget.download(url) + except Exception as error: + os.chdir(now_dir) + print(error) + return None + + for currentPath, _, zipFiles in os.walk(zips_path): + for Files in zipFiles: + filePart = Files.split(".") + extensionFile = filePart[len(filePart) - 1] + filePart.pop() + nameFile = "_".join(filePart) + realPath = os.path.join(currentPath, Files) + os.rename(realPath, nameFile + "." + extensionFile) + + os.chdir(now_dir) + return "downloaded" + + os.chdir(now_dir) + return None + + +def extract_and_show_progress(zipfile_path, unzips_path): + try: + with zipfile.ZipFile(zipfile_path, "r") as zip_ref: + for file_info in zip_ref.infolist(): + zip_ref.extract(file_info, unzips_path) + os.remove(zipfile_path) + return True + except Exception as error: + print(error) + return False + + +def unzip_file(zip_path, zip_file_name): + zip_file_path = os.path.join(zip_path, zip_file_name + ".zip") + extract_path = os.path.join(file_path, zip_file_name) + with zipfile.ZipFile(zip_file_path, "r") as zip_ref: + zip_ref.extractall(extract_path) + os.remove(zip_file_path) + + +def model_download_pipeline(url): + verify = download_from_url(url) + if verify == "downloaded": + extract_folder_path = "" + for filename in os.listdir(zips_path): + if filename.endswith(".zip"): + zipfile_path = os.path.join(zips_path, filename) + print("Proceeding with the extraction...") + + model_zip = os.path.basename(zipfile_path) + model_name = format_title(model_zip.split(".zip")[0]) + extract_folder_path = os.path.join( + "logs", + os.path.normpath(model_name), + ) + + success = extract_and_show_progress(zipfile_path, extract_folder_path) + + subfolders = [ + f + for f in os.listdir(extract_folder_path) + if os.path.isdir(os.path.join(extract_folder_path, f)) + ] + if len(subfolders) == 1: + subfolder_path = os.path.join(extract_folder_path, subfolders[0]) + for item in os.listdir(subfolder_path): + s = os.path.join(subfolder_path, item) + d = os.path.join(extract_folder_path, item) + shutil.move(s, d) + os.rmdir(subfolder_path) + + for item in os.listdir(extract_folder_path): + if ".pth" in item: + file_name = item.split(".pth")[0] + if file_name != model_name: + os.rename( + os.path.join(extract_folder_path, item), + os.path.join(extract_folder_path, model_name + ".pth"), + ) + else: + if "v2" not in item: + file_name = item.split("_nprobe_1_")[1].split("_v1")[0] + if file_name != model_name: + new_file_name = ( + item.split("_nprobe_1_")[0] + + "_nprobe_1_" + + model_name + + "_v1" + ) + os.rename( + os.path.join(extract_folder_path, item), + os.path.join( + extract_folder_path, new_file_name + ".index" + ), + ) + else: + file_name = item.split("_nprobe_1_")[1].split("_v2")[0] + if file_name != model_name: + new_file_name = ( + item.split("_nprobe_1_")[0] + + "_nprobe_1_" + + model_name + + "_v2" + ) + os.rename( + os.path.join(extract_folder_path, item), + os.path.join( + extract_folder_path, new_file_name + ".index" + ), + ) + + if success: + print(f"Model {model_name} downloaded!") + else: + print(f"Error downloading {model_name}") + sys.exit() + if extract_folder_path == "": + print("Zip file was not found.") + sys.exit() + result = search_pth_index(extract_folder_path) + else: + message = "Error" diff --git a/system/tts_engines/rvc/lib/tools/prerequisites_download.py b/system/tts_engines/rvc/lib/tools/prerequisites_download.py new file mode 100644 index 0000000..15ba882 --- /dev/null +++ b/system/tts_engines/rvc/lib/tools/prerequisites_download.py @@ -0,0 +1,96 @@ +import os +import wget + +url_base = "https://huggingface.co/nolinkhere" +pretraineds_v1_list = [ + ( + "pretrained_v1/", + [ + "D32k.pth", + "D40k.pth", + "D48k.pth", + "G32k.pth", + "G40k.pth", + "G48k.pth", + "f0D32k.pth", + "f0D40k.pth", + "f0D48k.pth", + "f0G32k.pth", + "f0G40k.pth", + "f0G48k.pth", + ], + ), +] +pretraineds_v2_list = [ + ( + "pretrained_v2/", + [ + "D32k.pth", + "D40k.pth", + "D48k.pth", + "G32k.pth", + "G40k.pth", + "G48k.pth", + "f0D32k.pth", + "f0D40k.pth", + "f0D48k.pth", + "f0G32k.pth", + "f0G40k.pth", + "f0G48k.pth", + ], + ), +] + +models_list = [ + "hubert_base.pt", + "contentvec_base.pt", + "rmvpe.pt", + "fcpe.pt", + # "rmvpe.onnx" +] + +executables_list = ["ffmpeg.exe", "ffprobe.exe"] + +folder_mapping_list = { + "pretrained_v1/": "rvc/pretraineds/pretrained_v1/", + "pretrained_v2/": "rvc/pretraineds/pretrained_v2/", +} + + +def prequisites_download_pipeline(pretraineds_v1, pretraineds_v2, models, exe): + def download_files(file_list): + for file_name in file_list: + destination_path = os.path.join(file_name) + url = f"{url_base}/{file_name}" + if not os.path.exists(destination_path): + os.makedirs(os.path.dirname(destination_path) or ".", exist_ok=True) + print(f"\nDownloading {url} to {destination_path}...") + wget.download(url, out=destination_path) + + if models == "True": + download_files(models_list) + + if exe == "True" and os.name == "nt": + download_files(executables_list) + + if pretraineds_v1 == "True": + for remote_folder, file_list in pretraineds_v1_list: + local_folder = folder_mapping_list.get(remote_folder, "") + for file in file_list: + destination_path = os.path.join(local_folder, file) + url = f"{url_base}/{remote_folder}{file}" + if not os.path.exists(destination_path): + os.makedirs(os.path.dirname(destination_path) or ".", exist_ok=True) + print(f"\nDownloading {url} to {destination_path}...") + wget.download(url, out=destination_path) + + if pretraineds_v2 == "True": + for remote_folder, file_list in pretraineds_v2_list: + local_folder = folder_mapping_list.get(remote_folder, "") + for file in file_list: + destination_path = os.path.join(local_folder, file) + url = f"{url_base}/{remote_folder}{file}" + if not os.path.exists(destination_path): + os.makedirs(os.path.dirname(destination_path) or ".", exist_ok=True) + print(f"\nDownloading {url} to {destination_path}...") + wget.download(url, out=destination_path) diff --git a/system/tts_engines/rvc/lib/tools/pretrained_selector.py b/system/tts_engines/rvc/lib/tools/pretrained_selector.py new file mode 100644 index 0000000..a6ff738 --- /dev/null +++ b/system/tts_engines/rvc/lib/tools/pretrained_selector.py @@ -0,0 +1,63 @@ +def pretrained_selector(pitch_guidance): + if pitch_guidance: + return { + "v1": { + "32000": ( + "rvc/pretraineds/pretrained_v1/f0G32k.pth", + "rvc/pretraineds/pretrained_v1/f0D32k.pth", + ), + "40000": ( + "rvc/pretraineds/pretrained_v1/f0G40k.pth", + "rvc/pretraineds/pretrained_v1/f0D40k.pth", + ), + "48000": ( + "rvc/pretraineds/pretrained_v1/f0G48k.pth", + "rvc/pretraineds/pretrained_v1/f0D48k.pth", + ), + }, + "v2": { + "32000": ( + "rvc/pretraineds/pretrained_v2/f0G32k.pth", + "rvc/pretraineds/pretrained_v2/f0D32k.pth", + ), + "40000": ( + "rvc/pretraineds/pretrained_v2/f0G40k.pth", + "rvc/pretraineds/pretrained_v2/f0D40k.pth", + ), + "48000": ( + "rvc/pretraineds/pretrained_v2/f0G48k.pth", + "rvc/pretraineds/pretrained_v2/f0D48k.pth", + ), + }, + } + else: + return { + "v1": { + "32000": ( + "rvc/pretraineds/pretrained_v1/G32k.pth", + "rvc/pretraineds/pretrained_v1/D32k.pth", + ), + "40000": ( + "rvc/pretraineds/pretrained_v1/G40k.pth", + "rvc/pretraineds/pretrained_v1/D40k.pth", + ), + "48000": ( + "rvc/pretraineds/pretrained_v1/G48k.pth", + "rvc/pretraineds/pretrained_v1/D48k.pth", + ), + }, + "v2": { + "32000": ( + "rvc/pretraineds/pretrained_v2/G32k.pth", + "rvc/pretraineds/pretrained_v2/D32k.pth", + ), + "40000": ( + "rvc/pretraineds/pretrained_v2/G40k.pth", + "rvc/pretraineds/pretrained_v2/D40k.pth", + ), + "48000": ( + "rvc/pretraineds/pretrained_v2/G48k.pth", + "rvc/pretraineds/pretrained_v2/D48k.pth", + ), + }, + } diff --git a/system/tts_engines/rvc/lib/tools/split_audio.py b/system/tts_engines/rvc/lib/tools/split_audio.py new file mode 100644 index 0000000..82089a3 --- /dev/null +++ b/system/tts_engines/rvc/lib/tools/split_audio.py @@ -0,0 +1,107 @@ +from pydub.silence import detect_nonsilent +from pydub import AudioSegment +import numpy as np +import re +import os + +from ...lib.utils import format_title + + +def process_audio(file_path): + try: + # load audio file + song = AudioSegment.from_file(file_path) + + # set silence threshold and duration + silence_thresh = -70 # dB + min_silence_len = 750 # ms, adjust as needed + + # detect nonsilent parts + nonsilent_parts = detect_nonsilent( + song, min_silence_len=min_silence_len, silence_thresh=silence_thresh + ) + + # Create a new directory to store chunks + file_dir = os.path.dirname(file_path) + file_name = os.path.basename(file_path).split(".")[0] + file_name = format_title(file_name) + new_dir_path = os.path.join(file_dir, file_name) + os.makedirs(new_dir_path, exist_ok=True) + + # Check if timestamps file exists, if so delete it + timestamps_file = os.path.join(file_dir, f"{file_name}_timestamps.txt") + if os.path.isfile(timestamps_file): + os.remove(timestamps_file) + + # export chunks and save start times + segment_count = 0 + for i, (start_i, end_i) in enumerate(nonsilent_parts): + chunk = song[start_i:end_i] + chunk_file_path = os.path.join(new_dir_path, f"chunk{i}.wav") + chunk.export(chunk_file_path, format="wav") + + print(f"Segment {i} created!") + segment_count += 1 + + # write start times to file + with open(timestamps_file, "a", encoding="utf-8") as f: + f.write(f"{chunk_file_path} starts at {start_i} ms\n") + + print(f"Total segments created: {segment_count}") + print(f"Split all chunks for {file_path} successfully!") + + return "Finish", new_dir_path + + except Exception as e: + print(f"An error occurred: {e}") + return "Error", None + + +def merge_audio(timestamps_file): + try: + # Extract prefix from the timestamps filename + prefix = os.path.basename(timestamps_file).replace("_timestamps.txt", "") + timestamps_dir = os.path.dirname(timestamps_file) + + # Open the timestamps file + with open(timestamps_file, "r", encoding="utf-8") as f: + lines = f.readlines() + + # Initialize empty list to hold audio segments + audio_segments = [] + last_end_time = 0 + + print(f"Processing file: {timestamps_file}") + + for line in lines: + # Extract filename and start time from line + match = re.search(r"(chunk\d+.wav) starts at (\d+) ms", line) + if match: + filename, start_time = match.groups() + start_time = int(start_time) + + # Construct the complete path to the chunk file + chunk_file = os.path.join(timestamps_dir, prefix, filename) + + # Add silence from last_end_time to start_time + silence_duration = max(start_time - last_end_time, 0) + silence = AudioSegment.silent(duration=silence_duration) + audio_segments.append(silence) + + # Load audio file and append to list + audio = AudioSegment.from_wav(chunk_file) + audio_segments.append(audio) + + # Update last_end_time + last_end_time = start_time + len(audio) + + print(f"Processed chunk: {chunk_file}") + + # Concatenate all audio_segments and export + merged_audio = sum(audio_segments) + merged_audio_np = np.array(merged_audio.get_array_of_samples()) + # print(f"Exported merged file: {merged_filename}\n") + return merged_audio.frame_rate, merged_audio_np + + except Exception as e: + print(f"An error occurred: {e}") diff --git a/system/tts_engines/rvc/lib/tools/tts.py b/system/tts_engines/rvc/lib/tools/tts.py new file mode 100644 index 0000000..7d8afab --- /dev/null +++ b/system/tts_engines/rvc/lib/tools/tts.py @@ -0,0 +1,19 @@ +import sys +import asyncio +import edge_tts + + +async def main(): + text = sys.argv[1] + voice = sys.argv[2] + rate = int(sys.argv[3]) + output_file = sys.argv[4] + + rates = f"+{rate}%" if rate >= 0 else f"{rate}%" + + await edge_tts.Communicate(text, voice, rate=rates).save(output_file) + print(f"TTS with {voice} completed. Output TTS file: '{output_file}'") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/system/tts_engines/rvc/lib/tools/tts_voices.json b/system/tts_engines/rvc/lib/tools/tts_voices.json new file mode 100644 index 0000000..ce6c03f --- /dev/null +++ b/system/tts_engines/rvc/lib/tools/tts_voices.json @@ -0,0 +1,5462 @@ +[ + { + "Name": "Microsoft Server Speech Text to Speech Voice (af-ZA, AdriNeural)", + "ShortName": "af-ZA-AdriNeural", + "Gender": "Female", + "Locale": "af-ZA", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Adri Online (Natural) - Afrikaans (South Africa)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (af-ZA, WillemNeural)", + "ShortName": "af-ZA-WillemNeural", + "Gender": "Male", + "Locale": "af-ZA", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Willem Online (Natural) - Afrikaans (South Africa)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (sq-AL, AnilaNeural)", + "ShortName": "sq-AL-AnilaNeural", + "Gender": "Female", + "Locale": "sq-AL", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Anila Online (Natural) - Albanian (Albania)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (sq-AL, IlirNeural)", + "ShortName": "sq-AL-IlirNeural", + "Gender": "Male", + "Locale": "sq-AL", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Ilir Online (Natural) - Albanian (Albania)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (am-ET, AmehaNeural)", + "ShortName": "am-ET-AmehaNeural", + "Gender": "Male", + "Locale": "am-ET", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Ameha Online (Natural) - Amharic (Ethiopia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (am-ET, MekdesNeural)", + "ShortName": "am-ET-MekdesNeural", + "Gender": "Female", + "Locale": "am-ET", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Mekdes Online (Natural) - Amharic (Ethiopia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ar-DZ, AminaNeural)", + "ShortName": "ar-DZ-AminaNeural", + "Gender": "Female", + "Locale": "ar-DZ", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Amina Online (Natural) - Arabic (Algeria)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ar-DZ, IsmaelNeural)", + "ShortName": "ar-DZ-IsmaelNeural", + "Gender": "Male", + "Locale": "ar-DZ", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Ismael Online (Natural) - Arabic (Algeria)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ar-BH, AliNeural)", + "ShortName": "ar-BH-AliNeural", + "Gender": "Male", + "Locale": "ar-BH", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Ali Online (Natural) - Arabic (Bahrain)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ar-BH, LailaNeural)", + "ShortName": "ar-BH-LailaNeural", + "Gender": "Female", + "Locale": "ar-BH", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Laila Online (Natural) - Arabic (Bahrain)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ar-EG, SalmaNeural)", + "ShortName": "ar-EG-SalmaNeural", + "Gender": "Female", + "Locale": "ar-EG", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Salma Online (Natural) - Arabic (Egypt)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ar-EG, ShakirNeural)", + "ShortName": "ar-EG-ShakirNeural", + "Gender": "Male", + "Locale": "ar-EG", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Shakir Online (Natural) - Arabic (Egypt)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ar-IQ, BasselNeural)", + "ShortName": "ar-IQ-BasselNeural", + "Gender": "Male", + "Locale": "ar-IQ", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Bassel Online (Natural) - Arabic (Iraq)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ar-IQ, RanaNeural)", + "ShortName": "ar-IQ-RanaNeural", + "Gender": "Female", + "Locale": "ar-IQ", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Rana Online (Natural) - Arabic (Iraq)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ar-JO, SanaNeural)", + "ShortName": "ar-JO-SanaNeural", + "Gender": "Female", + "Locale": "ar-JO", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Sana Online (Natural) - Arabic (Jordan)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ar-JO, TaimNeural)", + "ShortName": "ar-JO-TaimNeural", + "Gender": "Male", + "Locale": "ar-JO", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Taim Online (Natural) - Arabic (Jordan)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ar-KW, FahedNeural)", + "ShortName": "ar-KW-FahedNeural", + "Gender": "Male", + "Locale": "ar-KW", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Fahed Online (Natural) - Arabic (Kuwait)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ar-KW, NouraNeural)", + "ShortName": "ar-KW-NouraNeural", + "Gender": "Female", + "Locale": "ar-KW", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Noura Online (Natural) - Arabic (Kuwait)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ar-LB, LaylaNeural)", + "ShortName": "ar-LB-LaylaNeural", + "Gender": "Female", + "Locale": "ar-LB", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Layla Online (Natural) - Arabic (Lebanon)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ar-LB, RamiNeural)", + "ShortName": "ar-LB-RamiNeural", + "Gender": "Male", + "Locale": "ar-LB", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Rami Online (Natural) - Arabic (Lebanon)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ar-LY, ImanNeural)", + "ShortName": "ar-LY-ImanNeural", + "Gender": "Female", + "Locale": "ar-LY", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Iman Online (Natural) - Arabic (Libya)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ar-LY, OmarNeural)", + "ShortName": "ar-LY-OmarNeural", + "Gender": "Male", + "Locale": "ar-LY", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Omar Online (Natural) - Arabic (Libya)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ar-MA, JamalNeural)", + "ShortName": "ar-MA-JamalNeural", + "Gender": "Male", + "Locale": "ar-MA", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Jamal Online (Natural) - Arabic (Morocco)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ar-MA, MounaNeural)", + "ShortName": "ar-MA-MounaNeural", + "Gender": "Female", + "Locale": "ar-MA", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Mouna Online (Natural) - Arabic (Morocco)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ar-OM, AbdullahNeural)", + "ShortName": "ar-OM-AbdullahNeural", + "Gender": "Male", + "Locale": "ar-OM", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Abdullah Online (Natural) - Arabic (Oman)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ar-OM, AyshaNeural)", + "ShortName": "ar-OM-AyshaNeural", + "Gender": "Female", + "Locale": "ar-OM", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Aysha Online (Natural) - Arabic (Oman)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ar-QA, AmalNeural)", + "ShortName": "ar-QA-AmalNeural", + "Gender": "Female", + "Locale": "ar-QA", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Amal Online (Natural) - Arabic (Qatar)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ar-QA, MoazNeural)", + "ShortName": "ar-QA-MoazNeural", + "Gender": "Male", + "Locale": "ar-QA", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Moaz Online (Natural) - Arabic (Qatar)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ar-SA, HamedNeural)", + "ShortName": "ar-SA-HamedNeural", + "Gender": "Male", + "Locale": "ar-SA", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Hamed Online (Natural) - Arabic (Saudi Arabia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ar-SA, ZariyahNeural)", + "ShortName": "ar-SA-ZariyahNeural", + "Gender": "Female", + "Locale": "ar-SA", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Zariyah Online (Natural) - Arabic (Saudi Arabia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ar-SY, AmanyNeural)", + "ShortName": "ar-SY-AmanyNeural", + "Gender": "Female", + "Locale": "ar-SY", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Amany Online (Natural) - Arabic (Syria)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ar-SY, LaithNeural)", + "ShortName": "ar-SY-LaithNeural", + "Gender": "Male", + "Locale": "ar-SY", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Laith Online (Natural) - Arabic (Syria)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ar-TN, HediNeural)", + "ShortName": "ar-TN-HediNeural", + "Gender": "Male", + "Locale": "ar-TN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Hedi Online (Natural) - Arabic (Tunisia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ar-TN, ReemNeural)", + "ShortName": "ar-TN-ReemNeural", + "Gender": "Female", + "Locale": "ar-TN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Reem Online (Natural) - Arabic (Tunisia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ar-AE, FatimaNeural)", + "ShortName": "ar-AE-FatimaNeural", + "Gender": "Female", + "Locale": "ar-AE", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Fatima Online (Natural) - Arabic (United Arab Emirates)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ar-AE, HamdanNeural)", + "ShortName": "ar-AE-HamdanNeural", + "Gender": "Male", + "Locale": "ar-AE", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Hamdan Online (Natural) - Arabic (United Arab Emirates)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ar-YE, MaryamNeural)", + "ShortName": "ar-YE-MaryamNeural", + "Gender": "Female", + "Locale": "ar-YE", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Maryam Online (Natural) - Arabic (Yemen)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ar-YE, SalehNeural)", + "ShortName": "ar-YE-SalehNeural", + "Gender": "Male", + "Locale": "ar-YE", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Saleh Online (Natural) - Arabic (Yemen)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (az-AZ, BabekNeural)", + "ShortName": "az-AZ-BabekNeural", + "Gender": "Male", + "Locale": "az-AZ", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Babek Online (Natural) - Azerbaijani (Azerbaijan)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (az-AZ, BanuNeural)", + "ShortName": "az-AZ-BanuNeural", + "Gender": "Female", + "Locale": "az-AZ", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Banu Online (Natural) - Azerbaijani (Azerbaijan)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (bn-BD, NabanitaNeural)", + "ShortName": "bn-BD-NabanitaNeural", + "Gender": "Female", + "Locale": "bn-BD", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Nabanita Online (Natural) - Bangla (Bangladesh)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (bn-BD, PradeepNeural)", + "ShortName": "bn-BD-PradeepNeural", + "Gender": "Male", + "Locale": "bn-BD", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Pradeep Online (Natural) - Bangla (Bangladesh)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (bn-IN, BashkarNeural)", + "ShortName": "bn-IN-BashkarNeural", + "Gender": "Male", + "Locale": "bn-IN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Bashkar Online (Natural) - Bangla (India)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (bn-IN, TanishaaNeural)", + "ShortName": "bn-IN-TanishaaNeural", + "Gender": "Female", + "Locale": "bn-IN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Tanishaa Online (Natural) - Bengali (India)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (bs-BA, GoranNeural)", + "ShortName": "bs-BA-GoranNeural", + "Gender": "Male", + "Locale": "bs-BA", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Goran Online (Natural) - Bosnian (Bosnia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (bs-BA, VesnaNeural)", + "ShortName": "bs-BA-VesnaNeural", + "Gender": "Female", + "Locale": "bs-BA", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Vesna Online (Natural) - Bosnian (Bosnia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (bg-BG, BorislavNeural)", + "ShortName": "bg-BG-BorislavNeural", + "Gender": "Male", + "Locale": "bg-BG", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Borislav Online (Natural) - Bulgarian (Bulgaria)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (bg-BG, KalinaNeural)", + "ShortName": "bg-BG-KalinaNeural", + "Gender": "Female", + "Locale": "bg-BG", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Kalina Online (Natural) - Bulgarian (Bulgaria)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (my-MM, NilarNeural)", + "ShortName": "my-MM-NilarNeural", + "Gender": "Female", + "Locale": "my-MM", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Nilar Online (Natural) - Burmese (Myanmar)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (my-MM, ThihaNeural)", + "ShortName": "my-MM-ThihaNeural", + "Gender": "Male", + "Locale": "my-MM", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Thiha Online (Natural) - Burmese (Myanmar)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ca-ES, EnricNeural)", + "ShortName": "ca-ES-EnricNeural", + "Gender": "Male", + "Locale": "ca-ES", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Enric Online (Natural) - Catalan (Spain)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ca-ES, JoanaNeural)", + "ShortName": "ca-ES-JoanaNeural", + "Gender": "Female", + "Locale": "ca-ES", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Joana Online (Natural) - Catalan (Spain)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (zh-HK, HiuGaaiNeural)", + "ShortName": "zh-HK-HiuGaaiNeural", + "Gender": "Female", + "Locale": "zh-HK", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft HiuGaai Online (Natural) - Chinese (Cantonese Traditional)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (zh-HK, HiuMaanNeural)", + "ShortName": "zh-HK-HiuMaanNeural", + "Gender": "Female", + "Locale": "zh-HK", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft HiuMaan Online (Natural) - Chinese (Hong Kong)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (zh-HK, WanLungNeural)", + "ShortName": "zh-HK-WanLungNeural", + "Gender": "Male", + "Locale": "zh-HK", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft WanLung Online (Natural) - Chinese (Hong Kong)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (zh-CN, XiaoxiaoNeural)", + "ShortName": "zh-CN-XiaoxiaoNeural", + "Gender": "Female", + "Locale": "zh-CN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Xiaoxiao Online (Natural) - Chinese (Mainland)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "News", + "Novel" + ], + "VoicePersonalities": [ + "Warm" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (zh-CN, XiaoyiNeural)", + "ShortName": "zh-CN-XiaoyiNeural", + "Gender": "Female", + "Locale": "zh-CN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Xiaoyi Online (Natural) - Chinese (Mainland)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "Cartoon", + "Novel" + ], + "VoicePersonalities": [ + "Lively" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (zh-CN, YunjianNeural)", + "ShortName": "zh-CN-YunjianNeural", + "Gender": "Male", + "Locale": "zh-CN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Yunjian Online (Natural) - Chinese (Mainland)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "Sports", + " Novel" + ], + "VoicePersonalities": [ + "Passion" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (zh-CN, YunxiNeural)", + "ShortName": "zh-CN-YunxiNeural", + "Gender": "Male", + "Locale": "zh-CN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Yunxi Online (Natural) - Chinese (Mainland)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "Novel" + ], + "VoicePersonalities": [ + "Lively", + "Sunshine" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (zh-CN, YunxiaNeural)", + "ShortName": "zh-CN-YunxiaNeural", + "Gender": "Male", + "Locale": "zh-CN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Yunxia Online (Natural) - Chinese (Mainland)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "Cartoon", + "Novel" + ], + "VoicePersonalities": [ + "Cute" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (zh-CN, YunyangNeural)", + "ShortName": "zh-CN-YunyangNeural", + "Gender": "Male", + "Locale": "zh-CN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Yunyang Online (Natural) - Chinese (Mainland)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "News" + ], + "VoicePersonalities": [ + "Professional", + "Reliable" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (zh-CN-liaoning, XiaobeiNeural)", + "ShortName": "zh-CN-liaoning-XiaobeiNeural", + "Gender": "Female", + "Locale": "zh-CN-liaoning", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Xiaobei Online (Natural) - Chinese (Northeastern Mandarin)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "Dialect" + ], + "VoicePersonalities": [ + "Humorous" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (zh-TW, HsiaoChenNeural)", + "ShortName": "zh-TW-HsiaoChenNeural", + "Gender": "Female", + "Locale": "zh-TW", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft HsiaoChen Online (Natural) - Chinese (Taiwan)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (zh-TW, YunJheNeural)", + "ShortName": "zh-TW-YunJheNeural", + "Gender": "Male", + "Locale": "zh-TW", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft YunJhe Online (Natural) - Chinese (Taiwan)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (zh-TW, HsiaoYuNeural)", + "ShortName": "zh-TW-HsiaoYuNeural", + "Gender": "Female", + "Locale": "zh-TW", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft HsiaoYu Online (Natural) - Chinese (Taiwanese Mandarin)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (zh-CN-shaanxi, XiaoniNeural)", + "ShortName": "zh-CN-shaanxi-XiaoniNeural", + "Gender": "Female", + "Locale": "zh-CN-shaanxi", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Xiaoni Online (Natural) - Chinese (Zhongyuan Mandarin Shaanxi)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "Dialect" + ], + "VoicePersonalities": [ + "Bright" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (hr-HR, GabrijelaNeural)", + "ShortName": "hr-HR-GabrijelaNeural", + "Gender": "Female", + "Locale": "hr-HR", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Gabrijela Online (Natural) - Croatian (Croatia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (hr-HR, SreckoNeural)", + "ShortName": "hr-HR-SreckoNeural", + "Gender": "Male", + "Locale": "hr-HR", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Srecko Online (Natural) - Croatian (Croatia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (cs-CZ, AntoninNeural)", + "ShortName": "cs-CZ-AntoninNeural", + "Gender": "Male", + "Locale": "cs-CZ", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Antonin Online (Natural) - Czech (Czech)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (cs-CZ, VlastaNeural)", + "ShortName": "cs-CZ-VlastaNeural", + "Gender": "Female", + "Locale": "cs-CZ", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Vlasta Online (Natural) - Czech (Czech)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (da-DK, ChristelNeural)", + "ShortName": "da-DK-ChristelNeural", + "Gender": "Female", + "Locale": "da-DK", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Christel Online (Natural) - Danish (Denmark)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (da-DK, JeppeNeural)", + "ShortName": "da-DK-JeppeNeural", + "Gender": "Male", + "Locale": "da-DK", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Jeppe Online (Natural) - Danish (Denmark)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (nl-BE, ArnaudNeural)", + "ShortName": "nl-BE-ArnaudNeural", + "Gender": "Male", + "Locale": "nl-BE", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Arnaud Online (Natural) - Dutch (Belgium)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (nl-BE, DenaNeural)", + "ShortName": "nl-BE-DenaNeural", + "Gender": "Female", + "Locale": "nl-BE", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Dena Online (Natural) - Dutch (Belgium)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (nl-NL, ColetteNeural)", + "ShortName": "nl-NL-ColetteNeural", + "Gender": "Female", + "Locale": "nl-NL", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Colette Online (Natural) - Dutch (Netherlands)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (nl-NL, FennaNeural)", + "ShortName": "nl-NL-FennaNeural", + "Gender": "Female", + "Locale": "nl-NL", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Fenna Online (Natural) - Dutch (Netherlands)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (nl-NL, MaartenNeural)", + "ShortName": "nl-NL-MaartenNeural", + "Gender": "Male", + "Locale": "nl-NL", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Maarten Online (Natural) - Dutch (Netherlands)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-AU, NatashaNeural)", + "ShortName": "en-AU-NatashaNeural", + "Gender": "Female", + "Locale": "en-AU", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Natasha Online (Natural) - English (Australia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-AU, WilliamNeural)", + "ShortName": "en-AU-WilliamNeural", + "Gender": "Male", + "Locale": "en-AU", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft William Online (Natural) - English (Australia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-CA, ClaraNeural)", + "ShortName": "en-CA-ClaraNeural", + "Gender": "Female", + "Locale": "en-CA", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Clara Online (Natural) - English (Canada)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-CA, LiamNeural)", + "ShortName": "en-CA-LiamNeural", + "Gender": "Male", + "Locale": "en-CA", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Liam Online (Natural) - English (Canada)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-HK, SamNeural)", + "ShortName": "en-HK-SamNeural", + "Gender": "Male", + "Locale": "en-HK", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Sam Online (Natural) - English (Hongkong)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-HK, YanNeural)", + "ShortName": "en-HK-YanNeural", + "Gender": "Female", + "Locale": "en-HK", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Yan Online (Natural) - English (Hongkong)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-IN, NeerjaExpressiveNeural)", + "ShortName": "en-IN-NeerjaExpressiveNeural", + "Gender": "Female", + "Locale": "en-IN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Neerja Online (Natural) - English (India) (Preview)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-IN, NeerjaNeural)", + "ShortName": "en-IN-NeerjaNeural", + "Gender": "Female", + "Locale": "en-IN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Neerja Online (Natural) - English (India)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-IN, PrabhatNeural)", + "ShortName": "en-IN-PrabhatNeural", + "Gender": "Male", + "Locale": "en-IN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Prabhat Online (Natural) - English (India)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-IE, ConnorNeural)", + "ShortName": "en-IE-ConnorNeural", + "Gender": "Male", + "Locale": "en-IE", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Connor Online (Natural) - English (Ireland)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-IE, EmilyNeural)", + "ShortName": "en-IE-EmilyNeural", + "Gender": "Female", + "Locale": "en-IE", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Emily Online (Natural) - English (Ireland)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-KE, AsiliaNeural)", + "ShortName": "en-KE-AsiliaNeural", + "Gender": "Female", + "Locale": "en-KE", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Asilia Online (Natural) - English (Kenya)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-KE, ChilembaNeural)", + "ShortName": "en-KE-ChilembaNeural", + "Gender": "Male", + "Locale": "en-KE", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Chilemba Online (Natural) - English (Kenya)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-NZ, MitchellNeural)", + "ShortName": "en-NZ-MitchellNeural", + "Gender": "Male", + "Locale": "en-NZ", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Mitchell Online (Natural) - English (New Zealand)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-NZ, MollyNeural)", + "ShortName": "en-NZ-MollyNeural", + "Gender": "Female", + "Locale": "en-NZ", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Molly Online (Natural) - English (New Zealand)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-NG, AbeoNeural)", + "ShortName": "en-NG-AbeoNeural", + "Gender": "Male", + "Locale": "en-NG", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Abeo Online (Natural) - English (Nigeria)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-NG, EzinneNeural)", + "ShortName": "en-NG-EzinneNeural", + "Gender": "Female", + "Locale": "en-NG", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Ezinne Online (Natural) - English (Nigeria)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-PH, JamesNeural)", + "ShortName": "en-PH-JamesNeural", + "Gender": "Male", + "Locale": "en-PH", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft James Online (Natural) - English (Philippines)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-PH, RosaNeural)", + "ShortName": "en-PH-RosaNeural", + "Gender": "Female", + "Locale": "en-PH", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Rosa Online (Natural) - English (Philippines)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-SG, LunaNeural)", + "ShortName": "en-SG-LunaNeural", + "Gender": "Female", + "Locale": "en-SG", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Luna Online (Natural) - English (Singapore)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-SG, WayneNeural)", + "ShortName": "en-SG-WayneNeural", + "Gender": "Male", + "Locale": "en-SG", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Wayne Online (Natural) - English (Singapore)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-ZA, LeahNeural)", + "ShortName": "en-ZA-LeahNeural", + "Gender": "Female", + "Locale": "en-ZA", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Leah Online (Natural) - English (South Africa)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-ZA, LukeNeural)", + "ShortName": "en-ZA-LukeNeural", + "Gender": "Male", + "Locale": "en-ZA", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Luke Online (Natural) - English (South Africa)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-TZ, ElimuNeural)", + "ShortName": "en-TZ-ElimuNeural", + "Gender": "Male", + "Locale": "en-TZ", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Elimu Online (Natural) - English (Tanzania)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-TZ, ImaniNeural)", + "ShortName": "en-TZ-ImaniNeural", + "Gender": "Female", + "Locale": "en-TZ", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Imani Online (Natural) - English (Tanzania)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-GB, LibbyNeural)", + "ShortName": "en-GB-LibbyNeural", + "Gender": "Female", + "Locale": "en-GB", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Libby Online (Natural) - English (United Kingdom)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-GB, MaisieNeural)", + "ShortName": "en-GB-MaisieNeural", + "Gender": "Female", + "Locale": "en-GB", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Maisie Online (Natural) - English (United Kingdom)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-GB, RyanNeural)", + "ShortName": "en-GB-RyanNeural", + "Gender": "Male", + "Locale": "en-GB", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Ryan Online (Natural) - English (United Kingdom)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-GB, SoniaNeural)", + "ShortName": "en-GB-SoniaNeural", + "Gender": "Female", + "Locale": "en-GB", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Sonia Online (Natural) - English (United Kingdom)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-GB, ThomasNeural)", + "ShortName": "en-GB-ThomasNeural", + "Gender": "Male", + "Locale": "en-GB", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Thomas Online (Natural) - English (United Kingdom)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-US, AriaNeural)", + "ShortName": "en-US-AriaNeural", + "Gender": "Female", + "Locale": "en-US", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Aria Online (Natural) - English (United States)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "News", + "Novel" + ], + "VoicePersonalities": [ + "Positive", + "Confident" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-US, AnaNeural)", + "ShortName": "en-US-AnaNeural", + "Gender": "Female", + "Locale": "en-US", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Ana Online (Natural) - English (United States)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "Cartoon", + "Conversation" + ], + "VoicePersonalities": [ + "Cute" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-US, AndrewNeural)", + "ShortName": "en-US-AndrewNeural", + "Gender": "Male", + "Locale": "en-US", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Andrew Online (Natural) - English (United States)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "Conversation", + "Copilot" + ], + "VoicePersonalities": [ + "Warm", + "Confident", + "Engaging" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-US, AvaNeural)", + "ShortName": "en-US-AvaNeural", + "Gender": "Female", + "Locale": "en-US", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Ava Online (Natural) - English (United States)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "Conversation", + "Copilot" + ], + "VoicePersonalities": [ + "Pleasant", + "Genuine", + "Engaging" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-US, ChristopherNeural)", + "ShortName": "en-US-ChristopherNeural", + "Gender": "Male", + "Locale": "en-US", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Christopher Online (Natural) - English (United States)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "News", + "Novel" + ], + "VoicePersonalities": [ + "Reliable", + "Authority" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-US, EricNeural)", + "ShortName": "en-US-EricNeural", + "Gender": "Male", + "Locale": "en-US", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Eric Online (Natural) - English (United States)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "News", + "Novel" + ], + "VoicePersonalities": [ + "Rational" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-US, GuyNeural)", + "ShortName": "en-US-GuyNeural", + "Gender": "Male", + "Locale": "en-US", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Guy Online (Natural) - English (United States)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "News", + "Novel" + ], + "VoicePersonalities": [ + "Passion" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-US, JennyNeural)", + "ShortName": "en-US-JennyNeural", + "Gender": "Female", + "Locale": "en-US", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Jenny Online (Natural) - English (United States)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Considerate", + "Comfort" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-US, MichelleNeural)", + "ShortName": "en-US-MichelleNeural", + "Gender": "Female", + "Locale": "en-US", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Michelle Online (Natural) - English (United States)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "News", + "Novel" + ], + "VoicePersonalities": [ + "Friendly", + "Pleasant" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-US, RogerNeural)", + "ShortName": "en-US-RogerNeural", + "Gender": "Male", + "Locale": "en-US", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Roger Online (Natural) - English (United States)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "News", + "Novel" + ], + "VoicePersonalities": [ + "Lively" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (en-US, SteffanNeural)", + "ShortName": "en-US-SteffanNeural", + "Gender": "Male", + "Locale": "en-US", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Steffan Online (Natural) - English (United States)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "News", + "Novel" + ], + "VoicePersonalities": [ + "Rational" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (et-EE, AnuNeural)", + "ShortName": "et-EE-AnuNeural", + "Gender": "Female", + "Locale": "et-EE", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Anu Online (Natural) - Estonian (Estonia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (et-EE, KertNeural)", + "ShortName": "et-EE-KertNeural", + "Gender": "Male", + "Locale": "et-EE", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Kert Online (Natural) - Estonian (Estonia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (fil-PH, AngeloNeural)", + "ShortName": "fil-PH-AngeloNeural", + "Gender": "Male", + "Locale": "fil-PH", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Angelo Online (Natural) - Filipino (Philippines)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (fil-PH, BlessicaNeural)", + "ShortName": "fil-PH-BlessicaNeural", + "Gender": "Female", + "Locale": "fil-PH", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Blessica Online (Natural) - Filipino (Philippines)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (fi-FI, HarriNeural)", + "ShortName": "fi-FI-HarriNeural", + "Gender": "Male", + "Locale": "fi-FI", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Harri Online (Natural) - Finnish (Finland)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (fi-FI, NooraNeural)", + "ShortName": "fi-FI-NooraNeural", + "Gender": "Female", + "Locale": "fi-FI", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Noora Online (Natural) - Finnish (Finland)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (fr-BE, CharlineNeural)", + "ShortName": "fr-BE-CharlineNeural", + "Gender": "Female", + "Locale": "fr-BE", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Charline Online (Natural) - French (Belgium)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (fr-BE, GerardNeural)", + "ShortName": "fr-BE-GerardNeural", + "Gender": "Male", + "Locale": "fr-BE", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Gerard Online (Natural) - French (Belgium)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (fr-CA, AntoineNeural)", + "ShortName": "fr-CA-AntoineNeural", + "Gender": "Male", + "Locale": "fr-CA", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Antoine Online (Natural) - French (Canada)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (fr-CA, JeanNeural)", + "ShortName": "fr-CA-JeanNeural", + "Gender": "Male", + "Locale": "fr-CA", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Jean Online (Natural) - French (Canada)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (fr-CA, SylvieNeural)", + "ShortName": "fr-CA-SylvieNeural", + "Gender": "Female", + "Locale": "fr-CA", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Sylvie Online (Natural) - French (Canada)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (fr-FR, DeniseNeural)", + "ShortName": "fr-FR-DeniseNeural", + "Gender": "Female", + "Locale": "fr-FR", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Denise Online (Natural) - French (France)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (fr-FR, EloiseNeural)", + "ShortName": "fr-FR-EloiseNeural", + "Gender": "Female", + "Locale": "fr-FR", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Eloise Online (Natural) - French (France)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (fr-FR, HenriNeural)", + "ShortName": "fr-FR-HenriNeural", + "Gender": "Male", + "Locale": "fr-FR", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Henri Online (Natural) - French (France)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (fr-CH, ArianeNeural)", + "ShortName": "fr-CH-ArianeNeural", + "Gender": "Female", + "Locale": "fr-CH", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Ariane Online (Natural) - French (Switzerland)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (fr-CH, FabriceNeural)", + "ShortName": "fr-CH-FabriceNeural", + "Gender": "Male", + "Locale": "fr-CH", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Fabrice Online (Natural) - French (Switzerland)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (gl-ES, RoiNeural)", + "ShortName": "gl-ES-RoiNeural", + "Gender": "Male", + "Locale": "gl-ES", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Roi Online (Natural) - Galician (Spain)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (gl-ES, SabelaNeural)", + "ShortName": "gl-ES-SabelaNeural", + "Gender": "Female", + "Locale": "gl-ES", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Sabela Online (Natural) - Galician (Spain)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ka-GE, EkaNeural)", + "ShortName": "ka-GE-EkaNeural", + "Gender": "Female", + "Locale": "ka-GE", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Eka Online (Natural) - Georgian (Georgia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ka-GE, GiorgiNeural)", + "ShortName": "ka-GE-GiorgiNeural", + "Gender": "Male", + "Locale": "ka-GE", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Giorgi Online (Natural) - Georgian (Georgia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (de-AT, IngridNeural)", + "ShortName": "de-AT-IngridNeural", + "Gender": "Female", + "Locale": "de-AT", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Ingrid Online (Natural) - German (Austria)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (de-AT, JonasNeural)", + "ShortName": "de-AT-JonasNeural", + "Gender": "Male", + "Locale": "de-AT", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Jonas Online (Natural) - German (Austria)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (de-DE, AmalaNeural)", + "ShortName": "de-DE-AmalaNeural", + "Gender": "Female", + "Locale": "de-DE", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Amala Online (Natural) - German (Germany)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (de-DE, ConradNeural)", + "ShortName": "de-DE-ConradNeural", + "Gender": "Male", + "Locale": "de-DE", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Conrad Online (Natural) - German (Germany)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (de-DE, KatjaNeural)", + "ShortName": "de-DE-KatjaNeural", + "Gender": "Female", + "Locale": "de-DE", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Katja Online (Natural) - German (Germany)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (de-DE, KillianNeural)", + "ShortName": "de-DE-KillianNeural", + "Gender": "Male", + "Locale": "de-DE", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Killian Online (Natural) - German (Germany)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (de-CH, JanNeural)", + "ShortName": "de-CH-JanNeural", + "Gender": "Male", + "Locale": "de-CH", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Jan Online (Natural) - German (Switzerland)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (de-CH, LeniNeural)", + "ShortName": "de-CH-LeniNeural", + "Gender": "Female", + "Locale": "de-CH", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Leni Online (Natural) - German (Switzerland)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (el-GR, AthinaNeural)", + "ShortName": "el-GR-AthinaNeural", + "Gender": "Female", + "Locale": "el-GR", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Athina Online (Natural) - Greek (Greece)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (el-GR, NestorasNeural)", + "ShortName": "el-GR-NestorasNeural", + "Gender": "Male", + "Locale": "el-GR", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Nestoras Online (Natural) - Greek (Greece)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (gu-IN, DhwaniNeural)", + "ShortName": "gu-IN-DhwaniNeural", + "Gender": "Female", + "Locale": "gu-IN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Dhwani Online (Natural) - Gujarati (India)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (gu-IN, NiranjanNeural)", + "ShortName": "gu-IN-NiranjanNeural", + "Gender": "Male", + "Locale": "gu-IN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Niranjan Online (Natural) - Gujarati (India)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (he-IL, AvriNeural)", + "ShortName": "he-IL-AvriNeural", + "Gender": "Male", + "Locale": "he-IL", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Avri Online (Natural) - Hebrew (Israel)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (he-IL, HilaNeural)", + "ShortName": "he-IL-HilaNeural", + "Gender": "Female", + "Locale": "he-IL", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Hila Online (Natural) - Hebrew (Israel)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (hi-IN, MadhurNeural)", + "ShortName": "hi-IN-MadhurNeural", + "Gender": "Male", + "Locale": "hi-IN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Madhur Online (Natural) - Hindi (India)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (hi-IN, SwaraNeural)", + "ShortName": "hi-IN-SwaraNeural", + "Gender": "Female", + "Locale": "hi-IN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Swara Online (Natural) - Hindi (India)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (hu-HU, NoemiNeural)", + "ShortName": "hu-HU-NoemiNeural", + "Gender": "Female", + "Locale": "hu-HU", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Noemi Online (Natural) - Hungarian (Hungary)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (hu-HU, TamasNeural)", + "ShortName": "hu-HU-TamasNeural", + "Gender": "Male", + "Locale": "hu-HU", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Tamas Online (Natural) - Hungarian (Hungary)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (is-IS, GudrunNeural)", + "ShortName": "is-IS-GudrunNeural", + "Gender": "Female", + "Locale": "is-IS", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Gudrun Online (Natural) - Icelandic (Iceland)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (is-IS, GunnarNeural)", + "ShortName": "is-IS-GunnarNeural", + "Gender": "Male", + "Locale": "is-IS", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Gunnar Online (Natural) - Icelandic (Iceland)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (id-ID, ArdiNeural)", + "ShortName": "id-ID-ArdiNeural", + "Gender": "Male", + "Locale": "id-ID", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Ardi Online (Natural) - Indonesian (Indonesia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (id-ID, GadisNeural)", + "ShortName": "id-ID-GadisNeural", + "Gender": "Female", + "Locale": "id-ID", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Gadis Online (Natural) - Indonesian (Indonesia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ga-IE, ColmNeural)", + "ShortName": "ga-IE-ColmNeural", + "Gender": "Male", + "Locale": "ga-IE", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Colm Online (Natural) - Irish (Ireland)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ga-IE, OrlaNeural)", + "ShortName": "ga-IE-OrlaNeural", + "Gender": "Female", + "Locale": "ga-IE", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Orla Online (Natural) - Irish (Ireland)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (it-IT, DiegoNeural)", + "ShortName": "it-IT-DiegoNeural", + "Gender": "Male", + "Locale": "it-IT", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Diego Online (Natural) - Italian (Italy)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (it-IT, ElsaNeural)", + "ShortName": "it-IT-ElsaNeural", + "Gender": "Female", + "Locale": "it-IT", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Elsa Online (Natural) - Italian (Italy)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (it-IT, IsabellaNeural)", + "ShortName": "it-IT-IsabellaNeural", + "Gender": "Female", + "Locale": "it-IT", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Isabella Online (Natural) - Italian (Italy)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ja-JP, KeitaNeural)", + "ShortName": "ja-JP-KeitaNeural", + "Gender": "Male", + "Locale": "ja-JP", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Keita Online (Natural) - Japanese (Japan)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ja-JP, NanamiNeural)", + "ShortName": "ja-JP-NanamiNeural", + "Gender": "Female", + "Locale": "ja-JP", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Nanami Online (Natural) - Japanese (Japan)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (jv-ID, DimasNeural)", + "ShortName": "jv-ID-DimasNeural", + "Gender": "Male", + "Locale": "jv-ID", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Dimas Online (Natural) - Javanese (Indonesia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (jv-ID, SitiNeural)", + "ShortName": "jv-ID-SitiNeural", + "Gender": "Female", + "Locale": "jv-ID", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Siti Online (Natural) - Javanese (Indonesia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (kn-IN, GaganNeural)", + "ShortName": "kn-IN-GaganNeural", + "Gender": "Male", + "Locale": "kn-IN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Gagan Online (Natural) - Kannada (India)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (kn-IN, SapnaNeural)", + "ShortName": "kn-IN-SapnaNeural", + "Gender": "Female", + "Locale": "kn-IN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Sapna Online (Natural) - Kannada (India)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (kk-KZ, AigulNeural)", + "ShortName": "kk-KZ-AigulNeural", + "Gender": "Female", + "Locale": "kk-KZ", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Aigul Online (Natural) - Kazakh (Kazakhstan)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (kk-KZ, DauletNeural)", + "ShortName": "kk-KZ-DauletNeural", + "Gender": "Male", + "Locale": "kk-KZ", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Daulet Online (Natural) - Kazakh (Kazakhstan)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (km-KH, PisethNeural)", + "ShortName": "km-KH-PisethNeural", + "Gender": "Male", + "Locale": "km-KH", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Piseth Online (Natural) - Khmer (Cambodia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (km-KH, SreymomNeural)", + "ShortName": "km-KH-SreymomNeural", + "Gender": "Female", + "Locale": "km-KH", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Sreymom Online (Natural) - Khmer (Cambodia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ko-KR, InJoonNeural)", + "ShortName": "ko-KR-InJoonNeural", + "Gender": "Male", + "Locale": "ko-KR", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft InJoon Online (Natural) - Korean (Korea)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ko-KR, SunHiNeural)", + "ShortName": "ko-KR-SunHiNeural", + "Gender": "Female", + "Locale": "ko-KR", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft SunHi Online (Natural) - Korean (Korea)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (lo-LA, ChanthavongNeural)", + "ShortName": "lo-LA-ChanthavongNeural", + "Gender": "Male", + "Locale": "lo-LA", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Chanthavong Online (Natural) - Lao (Laos)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (lo-LA, KeomanyNeural)", + "ShortName": "lo-LA-KeomanyNeural", + "Gender": "Female", + "Locale": "lo-LA", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Keomany Online (Natural) - Lao (Laos)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (lv-LV, EveritaNeural)", + "ShortName": "lv-LV-EveritaNeural", + "Gender": "Female", + "Locale": "lv-LV", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Everita Online (Natural) - Latvian (Latvia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (lv-LV, NilsNeural)", + "ShortName": "lv-LV-NilsNeural", + "Gender": "Male", + "Locale": "lv-LV", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Nils Online (Natural) - Latvian (Latvia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (lt-LT, LeonasNeural)", + "ShortName": "lt-LT-LeonasNeural", + "Gender": "Male", + "Locale": "lt-LT", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Leonas Online (Natural) - Lithuanian (Lithuania)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (lt-LT, OnaNeural)", + "ShortName": "lt-LT-OnaNeural", + "Gender": "Female", + "Locale": "lt-LT", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Ona Online (Natural) - Lithuanian (Lithuania)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (mk-MK, AleksandarNeural)", + "ShortName": "mk-MK-AleksandarNeural", + "Gender": "Male", + "Locale": "mk-MK", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Aleksandar Online (Natural) - Macedonian (Republic of North Macedonia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (mk-MK, MarijaNeural)", + "ShortName": "mk-MK-MarijaNeural", + "Gender": "Female", + "Locale": "mk-MK", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Marija Online (Natural) - Macedonian (Republic of North Macedonia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ms-MY, OsmanNeural)", + "ShortName": "ms-MY-OsmanNeural", + "Gender": "Male", + "Locale": "ms-MY", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Osman Online (Natural) - Malay (Malaysia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ms-MY, YasminNeural)", + "ShortName": "ms-MY-YasminNeural", + "Gender": "Female", + "Locale": "ms-MY", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Yasmin Online (Natural) - Malay (Malaysia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ml-IN, MidhunNeural)", + "ShortName": "ml-IN-MidhunNeural", + "Gender": "Male", + "Locale": "ml-IN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Midhun Online (Natural) - Malayalam (India)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ml-IN, SobhanaNeural)", + "ShortName": "ml-IN-SobhanaNeural", + "Gender": "Female", + "Locale": "ml-IN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Sobhana Online (Natural) - Malayalam (India)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (mt-MT, GraceNeural)", + "ShortName": "mt-MT-GraceNeural", + "Gender": "Female", + "Locale": "mt-MT", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Grace Online (Natural) - Maltese (Malta)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (mt-MT, JosephNeural)", + "ShortName": "mt-MT-JosephNeural", + "Gender": "Male", + "Locale": "mt-MT", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Joseph Online (Natural) - Maltese (Malta)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (mr-IN, AarohiNeural)", + "ShortName": "mr-IN-AarohiNeural", + "Gender": "Female", + "Locale": "mr-IN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Aarohi Online (Natural) - Marathi (India)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (mr-IN, ManoharNeural)", + "ShortName": "mr-IN-ManoharNeural", + "Gender": "Male", + "Locale": "mr-IN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Manohar Online (Natural) - Marathi (India)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (mn-MN, BataaNeural)", + "ShortName": "mn-MN-BataaNeural", + "Gender": "Male", + "Locale": "mn-MN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Bataa Online (Natural) - Mongolian (Mongolia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (mn-MN, YesuiNeural)", + "ShortName": "mn-MN-YesuiNeural", + "Gender": "Female", + "Locale": "mn-MN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Yesui Online (Natural) - Mongolian (Mongolia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ne-NP, HemkalaNeural)", + "ShortName": "ne-NP-HemkalaNeural", + "Gender": "Female", + "Locale": "ne-NP", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Hemkala Online (Natural) - Nepali (Nepal)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ne-NP, SagarNeural)", + "ShortName": "ne-NP-SagarNeural", + "Gender": "Male", + "Locale": "ne-NP", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Sagar Online (Natural) - Nepali (Nepal)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (nb-NO, FinnNeural)", + "ShortName": "nb-NO-FinnNeural", + "Gender": "Male", + "Locale": "nb-NO", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Finn Online (Natural) - Norwegian (Bokmål Norway)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (nb-NO, PernilleNeural)", + "ShortName": "nb-NO-PernilleNeural", + "Gender": "Female", + "Locale": "nb-NO", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Pernille Online (Natural) - Norwegian (Bokmål, Norway)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ps-AF, GulNawazNeural)", + "ShortName": "ps-AF-GulNawazNeural", + "Gender": "Male", + "Locale": "ps-AF", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft GulNawaz Online (Natural) - Pashto (Afghanistan)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ps-AF, LatifaNeural)", + "ShortName": "ps-AF-LatifaNeural", + "Gender": "Female", + "Locale": "ps-AF", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Latifa Online (Natural) - Pashto (Afghanistan)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (fa-IR, DilaraNeural)", + "ShortName": "fa-IR-DilaraNeural", + "Gender": "Female", + "Locale": "fa-IR", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Dilara Online (Natural) - Persian (Iran)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (fa-IR, FaridNeural)", + "ShortName": "fa-IR-FaridNeural", + "Gender": "Male", + "Locale": "fa-IR", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Farid Online (Natural) - Persian (Iran)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (pl-PL, MarekNeural)", + "ShortName": "pl-PL-MarekNeural", + "Gender": "Male", + "Locale": "pl-PL", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Marek Online (Natural) - Polish (Poland)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (pl-PL, ZofiaNeural)", + "ShortName": "pl-PL-ZofiaNeural", + "Gender": "Female", + "Locale": "pl-PL", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Zofia Online (Natural) - Polish (Poland)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (pt-BR, AntonioNeural)", + "ShortName": "pt-BR-AntonioNeural", + "Gender": "Male", + "Locale": "pt-BR", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Antonio Online (Natural) - Portuguese (Brazil)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (pt-BR, FranciscaNeural)", + "ShortName": "pt-BR-FranciscaNeural", + "Gender": "Female", + "Locale": "pt-BR", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Francisca Online (Natural) - Portuguese (Brazil)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (pt-PT, DuarteNeural)", + "ShortName": "pt-PT-DuarteNeural", + "Gender": "Male", + "Locale": "pt-PT", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Duarte Online (Natural) - Portuguese (Portugal)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (pt-PT, RaquelNeural)", + "ShortName": "pt-PT-RaquelNeural", + "Gender": "Female", + "Locale": "pt-PT", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Raquel Online (Natural) - Portuguese (Portugal)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ro-RO, AlinaNeural)", + "ShortName": "ro-RO-AlinaNeural", + "Gender": "Female", + "Locale": "ro-RO", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Alina Online (Natural) - Romanian (Romania)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ro-RO, EmilNeural)", + "ShortName": "ro-RO-EmilNeural", + "Gender": "Male", + "Locale": "ro-RO", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Emil Online (Natural) - Romanian (Romania)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ru-RU, DmitryNeural)", + "ShortName": "ru-RU-DmitryNeural", + "Gender": "Male", + "Locale": "ru-RU", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Dmitry Online (Natural) - Russian (Russia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ru-RU, SvetlanaNeural)", + "ShortName": "ru-RU-SvetlanaNeural", + "Gender": "Female", + "Locale": "ru-RU", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Svetlana Online (Natural) - Russian (Russia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (sr-RS, NicholasNeural)", + "ShortName": "sr-RS-NicholasNeural", + "Gender": "Male", + "Locale": "sr-RS", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Nicholas Online (Natural) - Serbian (Serbia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (sr-RS, SophieNeural)", + "ShortName": "sr-RS-SophieNeural", + "Gender": "Female", + "Locale": "sr-RS", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Sophie Online (Natural) - Serbian (Serbia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (si-LK, SameeraNeural)", + "ShortName": "si-LK-SameeraNeural", + "Gender": "Male", + "Locale": "si-LK", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Sameera Online (Natural) - Sinhala (Sri Lanka)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (si-LK, ThiliniNeural)", + "ShortName": "si-LK-ThiliniNeural", + "Gender": "Female", + "Locale": "si-LK", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Thilini Online (Natural) - Sinhala (Sri Lanka)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (sk-SK, LukasNeural)", + "ShortName": "sk-SK-LukasNeural", + "Gender": "Male", + "Locale": "sk-SK", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Lukas Online (Natural) - Slovak (Slovakia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (sk-SK, ViktoriaNeural)", + "ShortName": "sk-SK-ViktoriaNeural", + "Gender": "Female", + "Locale": "sk-SK", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Viktoria Online (Natural) - Slovak (Slovakia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (sl-SI, PetraNeural)", + "ShortName": "sl-SI-PetraNeural", + "Gender": "Female", + "Locale": "sl-SI", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Petra Online (Natural) - Slovenian (Slovenia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (sl-SI, RokNeural)", + "ShortName": "sl-SI-RokNeural", + "Gender": "Male", + "Locale": "sl-SI", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Rok Online (Natural) - Slovenian (Slovenia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (so-SO, MuuseNeural)", + "ShortName": "so-SO-MuuseNeural", + "Gender": "Male", + "Locale": "so-SO", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Muuse Online (Natural) - Somali (Somalia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (so-SO, UbaxNeural)", + "ShortName": "so-SO-UbaxNeural", + "Gender": "Female", + "Locale": "so-SO", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Ubax Online (Natural) - Somali (Somalia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-AR, ElenaNeural)", + "ShortName": "es-AR-ElenaNeural", + "Gender": "Female", + "Locale": "es-AR", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Elena Online (Natural) - Spanish (Argentina)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-AR, TomasNeural)", + "ShortName": "es-AR-TomasNeural", + "Gender": "Male", + "Locale": "es-AR", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Tomas Online (Natural) - Spanish (Argentina)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-BO, MarceloNeural)", + "ShortName": "es-BO-MarceloNeural", + "Gender": "Male", + "Locale": "es-BO", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Marcelo Online (Natural) - Spanish (Bolivia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-BO, SofiaNeural)", + "ShortName": "es-BO-SofiaNeural", + "Gender": "Female", + "Locale": "es-BO", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Sofia Online (Natural) - Spanish (Bolivia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-CL, CatalinaNeural)", + "ShortName": "es-CL-CatalinaNeural", + "Gender": "Female", + "Locale": "es-CL", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Catalina Online (Natural) - Spanish (Chile)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-CL, LorenzoNeural)", + "ShortName": "es-CL-LorenzoNeural", + "Gender": "Male", + "Locale": "es-CL", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Lorenzo Online (Natural) - Spanish (Chile)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-CO, GonzaloNeural)", + "ShortName": "es-CO-GonzaloNeural", + "Gender": "Male", + "Locale": "es-CO", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Gonzalo Online (Natural) - Spanish (Colombia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-CO, SalomeNeural)", + "ShortName": "es-CO-SalomeNeural", + "Gender": "Female", + "Locale": "es-CO", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Salome Online (Natural) - Spanish (Colombia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-CR, JuanNeural)", + "ShortName": "es-CR-JuanNeural", + "Gender": "Male", + "Locale": "es-CR", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Juan Online (Natural) - Spanish (Costa Rica)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-CR, MariaNeural)", + "ShortName": "es-CR-MariaNeural", + "Gender": "Female", + "Locale": "es-CR", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Maria Online (Natural) - Spanish (Costa Rica)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-CU, BelkysNeural)", + "ShortName": "es-CU-BelkysNeural", + "Gender": "Female", + "Locale": "es-CU", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Belkys Online (Natural) - Spanish (Cuba)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-CU, ManuelNeural)", + "ShortName": "es-CU-ManuelNeural", + "Gender": "Male", + "Locale": "es-CU", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Manuel Online (Natural) - Spanish (Cuba)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-DO, EmilioNeural)", + "ShortName": "es-DO-EmilioNeural", + "Gender": "Male", + "Locale": "es-DO", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Emilio Online (Natural) - Spanish (Dominican Republic)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-DO, RamonaNeural)", + "ShortName": "es-DO-RamonaNeural", + "Gender": "Female", + "Locale": "es-DO", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Ramona Online (Natural) - Spanish (Dominican Republic)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-EC, AndreaNeural)", + "ShortName": "es-EC-AndreaNeural", + "Gender": "Female", + "Locale": "es-EC", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Andrea Online (Natural) - Spanish (Ecuador)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-EC, LuisNeural)", + "ShortName": "es-EC-LuisNeural", + "Gender": "Male", + "Locale": "es-EC", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Luis Online (Natural) - Spanish (Ecuador)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-SV, LorenaNeural)", + "ShortName": "es-SV-LorenaNeural", + "Gender": "Female", + "Locale": "es-SV", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Lorena Online (Natural) - Spanish (El Salvador)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-SV, RodrigoNeural)", + "ShortName": "es-SV-RodrigoNeural", + "Gender": "Male", + "Locale": "es-SV", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Rodrigo Online (Natural) - Spanish (El Salvador)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-GQ, JavierNeural)", + "ShortName": "es-GQ-JavierNeural", + "Gender": "Male", + "Locale": "es-GQ", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Javier Online (Natural) - Spanish (Equatorial Guinea)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-GQ, TeresaNeural)", + "ShortName": "es-GQ-TeresaNeural", + "Gender": "Female", + "Locale": "es-GQ", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Teresa Online (Natural) - Spanish (Equatorial Guinea)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-GT, AndresNeural)", + "ShortName": "es-GT-AndresNeural", + "Gender": "Male", + "Locale": "es-GT", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Andres Online (Natural) - Spanish (Guatemala)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-GT, MartaNeural)", + "ShortName": "es-GT-MartaNeural", + "Gender": "Female", + "Locale": "es-GT", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Marta Online (Natural) - Spanish (Guatemala)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-HN, CarlosNeural)", + "ShortName": "es-HN-CarlosNeural", + "Gender": "Male", + "Locale": "es-HN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Carlos Online (Natural) - Spanish (Honduras)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-HN, KarlaNeural)", + "ShortName": "es-HN-KarlaNeural", + "Gender": "Female", + "Locale": "es-HN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Karla Online (Natural) - Spanish (Honduras)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-MX, DaliaNeural)", + "ShortName": "es-MX-DaliaNeural", + "Gender": "Female", + "Locale": "es-MX", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Dalia Online (Natural) - Spanish (Mexico)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-MX, JorgeNeural)", + "ShortName": "es-MX-JorgeNeural", + "Gender": "Male", + "Locale": "es-MX", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Jorge Online (Natural) - Spanish (Mexico)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-NI, FedericoNeural)", + "ShortName": "es-NI-FedericoNeural", + "Gender": "Male", + "Locale": "es-NI", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Federico Online (Natural) - Spanish (Nicaragua)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-NI, YolandaNeural)", + "ShortName": "es-NI-YolandaNeural", + "Gender": "Female", + "Locale": "es-NI", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Yolanda Online (Natural) - Spanish (Nicaragua)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-PA, MargaritaNeural)", + "ShortName": "es-PA-MargaritaNeural", + "Gender": "Female", + "Locale": "es-PA", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Margarita Online (Natural) - Spanish (Panama)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-PA, RobertoNeural)", + "ShortName": "es-PA-RobertoNeural", + "Gender": "Male", + "Locale": "es-PA", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Roberto Online (Natural) - Spanish (Panama)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-PY, MarioNeural)", + "ShortName": "es-PY-MarioNeural", + "Gender": "Male", + "Locale": "es-PY", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Mario Online (Natural) - Spanish (Paraguay)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-PY, TaniaNeural)", + "ShortName": "es-PY-TaniaNeural", + "Gender": "Female", + "Locale": "es-PY", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Tania Online (Natural) - Spanish (Paraguay)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-PE, AlexNeural)", + "ShortName": "es-PE-AlexNeural", + "Gender": "Male", + "Locale": "es-PE", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Alex Online (Natural) - Spanish (Peru)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-PE, CamilaNeural)", + "ShortName": "es-PE-CamilaNeural", + "Gender": "Female", + "Locale": "es-PE", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Camila Online (Natural) - Spanish (Peru)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-PR, KarinaNeural)", + "ShortName": "es-PR-KarinaNeural", + "Gender": "Female", + "Locale": "es-PR", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Karina Online (Natural) - Spanish (Puerto Rico)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-PR, VictorNeural)", + "ShortName": "es-PR-VictorNeural", + "Gender": "Male", + "Locale": "es-PR", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Victor Online (Natural) - Spanish (Puerto Rico)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-ES, AlvaroNeural)", + "ShortName": "es-ES-AlvaroNeural", + "Gender": "Male", + "Locale": "es-ES", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Alvaro Online (Natural) - Spanish (Spain)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-ES, ElviraNeural)", + "ShortName": "es-ES-ElviraNeural", + "Gender": "Female", + "Locale": "es-ES", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Elvira Online (Natural) - Spanish (Spain)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-US, AlonsoNeural)", + "ShortName": "es-US-AlonsoNeural", + "Gender": "Male", + "Locale": "es-US", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Alonso Online (Natural) - Spanish (United States)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-US, PalomaNeural)", + "ShortName": "es-US-PalomaNeural", + "Gender": "Female", + "Locale": "es-US", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Paloma Online (Natural) - Spanish (United States)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-UY, MateoNeural)", + "ShortName": "es-UY-MateoNeural", + "Gender": "Male", + "Locale": "es-UY", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Mateo Online (Natural) - Spanish (Uruguay)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-UY, ValentinaNeural)", + "ShortName": "es-UY-ValentinaNeural", + "Gender": "Female", + "Locale": "es-UY", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Valentina Online (Natural) - Spanish (Uruguay)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-VE, PaolaNeural)", + "ShortName": "es-VE-PaolaNeural", + "Gender": "Female", + "Locale": "es-VE", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Paola Online (Natural) - Spanish (Venezuela)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (es-VE, SebastianNeural)", + "ShortName": "es-VE-SebastianNeural", + "Gender": "Male", + "Locale": "es-VE", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Sebastian Online (Natural) - Spanish (Venezuela)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (su-ID, JajangNeural)", + "ShortName": "su-ID-JajangNeural", + "Gender": "Male", + "Locale": "su-ID", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Jajang Online (Natural) - Sundanese (Indonesia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (su-ID, TutiNeural)", + "ShortName": "su-ID-TutiNeural", + "Gender": "Female", + "Locale": "su-ID", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Tuti Online (Natural) - Sundanese (Indonesia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (sw-KE, RafikiNeural)", + "ShortName": "sw-KE-RafikiNeural", + "Gender": "Male", + "Locale": "sw-KE", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Rafiki Online (Natural) - Swahili (Kenya)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (sw-KE, ZuriNeural)", + "ShortName": "sw-KE-ZuriNeural", + "Gender": "Female", + "Locale": "sw-KE", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Zuri Online (Natural) - Swahili (Kenya)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (sw-TZ, DaudiNeural)", + "ShortName": "sw-TZ-DaudiNeural", + "Gender": "Male", + "Locale": "sw-TZ", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Daudi Online (Natural) - Swahili (Tanzania)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (sw-TZ, RehemaNeural)", + "ShortName": "sw-TZ-RehemaNeural", + "Gender": "Female", + "Locale": "sw-TZ", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Rehema Online (Natural) - Swahili (Tanzania)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (sv-SE, MattiasNeural)", + "ShortName": "sv-SE-MattiasNeural", + "Gender": "Male", + "Locale": "sv-SE", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Mattias Online (Natural) - Swedish (Sweden)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (sv-SE, SofieNeural)", + "ShortName": "sv-SE-SofieNeural", + "Gender": "Female", + "Locale": "sv-SE", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Sofie Online (Natural) - Swedish (Sweden)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ta-IN, PallaviNeural)", + "ShortName": "ta-IN-PallaviNeural", + "Gender": "Female", + "Locale": "ta-IN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Pallavi Online (Natural) - Tamil (India)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ta-IN, ValluvarNeural)", + "ShortName": "ta-IN-ValluvarNeural", + "Gender": "Male", + "Locale": "ta-IN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Valluvar Online (Natural) - Tamil (India)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ta-MY, KaniNeural)", + "ShortName": "ta-MY-KaniNeural", + "Gender": "Female", + "Locale": "ta-MY", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Kani Online (Natural) - Tamil (Malaysia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ta-MY, SuryaNeural)", + "ShortName": "ta-MY-SuryaNeural", + "Gender": "Male", + "Locale": "ta-MY", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Surya Online (Natural) - Tamil (Malaysia)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ta-SG, AnbuNeural)", + "ShortName": "ta-SG-AnbuNeural", + "Gender": "Male", + "Locale": "ta-SG", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Anbu Online (Natural) - Tamil (Singapore)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ta-SG, VenbaNeural)", + "ShortName": "ta-SG-VenbaNeural", + "Gender": "Female", + "Locale": "ta-SG", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Venba Online (Natural) - Tamil (Singapore)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ta-LK, KumarNeural)", + "ShortName": "ta-LK-KumarNeural", + "Gender": "Male", + "Locale": "ta-LK", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Kumar Online (Natural) - Tamil (Sri Lanka)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ta-LK, SaranyaNeural)", + "ShortName": "ta-LK-SaranyaNeural", + "Gender": "Female", + "Locale": "ta-LK", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Saranya Online (Natural) - Tamil (Sri Lanka)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (te-IN, MohanNeural)", + "ShortName": "te-IN-MohanNeural", + "Gender": "Male", + "Locale": "te-IN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Mohan Online (Natural) - Telugu (India)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (te-IN, ShrutiNeural)", + "ShortName": "te-IN-ShrutiNeural", + "Gender": "Female", + "Locale": "te-IN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Shruti Online (Natural) - Telugu (India)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (th-TH, NiwatNeural)", + "ShortName": "th-TH-NiwatNeural", + "Gender": "Male", + "Locale": "th-TH", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Niwat Online (Natural) - Thai (Thailand)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (th-TH, PremwadeeNeural)", + "ShortName": "th-TH-PremwadeeNeural", + "Gender": "Female", + "Locale": "th-TH", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Premwadee Online (Natural) - Thai (Thailand)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (tr-TR, AhmetNeural)", + "ShortName": "tr-TR-AhmetNeural", + "Gender": "Male", + "Locale": "tr-TR", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Ahmet Online (Natural) - Turkish (Turkey)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (tr-TR, EmelNeural)", + "ShortName": "tr-TR-EmelNeural", + "Gender": "Female", + "Locale": "tr-TR", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Emel Online (Natural) - Turkish (Turkey)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (uk-UA, OstapNeural)", + "ShortName": "uk-UA-OstapNeural", + "Gender": "Male", + "Locale": "uk-UA", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Ostap Online (Natural) - Ukrainian (Ukraine)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (uk-UA, PolinaNeural)", + "ShortName": "uk-UA-PolinaNeural", + "Gender": "Female", + "Locale": "uk-UA", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Polina Online (Natural) - Ukrainian (Ukraine)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ur-IN, GulNeural)", + "ShortName": "ur-IN-GulNeural", + "Gender": "Female", + "Locale": "ur-IN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Gul Online (Natural) - Urdu (India)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ur-IN, SalmanNeural)", + "ShortName": "ur-IN-SalmanNeural", + "Gender": "Male", + "Locale": "ur-IN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Salman Online (Natural) - Urdu (India)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ur-PK, AsadNeural)", + "ShortName": "ur-PK-AsadNeural", + "Gender": "Male", + "Locale": "ur-PK", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Asad Online (Natural) - Urdu (Pakistan)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (ur-PK, UzmaNeural)", + "ShortName": "ur-PK-UzmaNeural", + "Gender": "Female", + "Locale": "ur-PK", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Uzma Online (Natural) - Urdu (Pakistan)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (uz-UZ, MadinaNeural)", + "ShortName": "uz-UZ-MadinaNeural", + "Gender": "Female", + "Locale": "uz-UZ", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Madina Online (Natural) - Uzbek (Uzbekistan)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (uz-UZ, SardorNeural)", + "ShortName": "uz-UZ-SardorNeural", + "Gender": "Male", + "Locale": "uz-UZ", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Sardor Online (Natural) - Uzbek (Uzbekistan)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (vi-VN, HoaiMyNeural)", + "ShortName": "vi-VN-HoaiMyNeural", + "Gender": "Female", + "Locale": "vi-VN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft HoaiMy Online (Natural) - Vietnamese (Vietnam)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (vi-VN, NamMinhNeural)", + "ShortName": "vi-VN-NamMinhNeural", + "Gender": "Male", + "Locale": "vi-VN", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft NamMinh Online (Natural) - Vietnamese (Vietnam)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (cy-GB, AledNeural)", + "ShortName": "cy-GB-AledNeural", + "Gender": "Male", + "Locale": "cy-GB", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Aled Online (Natural) - Welsh (United Kingdom)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (cy-GB, NiaNeural)", + "ShortName": "cy-GB-NiaNeural", + "Gender": "Female", + "Locale": "cy-GB", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Nia Online (Natural) - Welsh (United Kingdom)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (zu-ZA, ThandoNeural)", + "ShortName": "zu-ZA-ThandoNeural", + "Gender": "Female", + "Locale": "zu-ZA", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Thando Online (Natural) - Zulu (South Africa)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + }, + { + "Name": "Microsoft Server Speech Text to Speech Voice (zu-ZA, ThembaNeural)", + "ShortName": "zu-ZA-ThembaNeural", + "Gender": "Male", + "Locale": "zu-ZA", + "SuggestedCodec": "audio-24khz-48kbitrate-mono-mp3", + "FriendlyName": "Microsoft Themba Online (Natural) - Zulu (South Africa)", + "Status": "GA", + "VoiceTag": { + "ContentCategories": [ + "General" + ], + "VoicePersonalities": [ + "Friendly", + "Positive" + ] + } + } +] \ No newline at end of file diff --git a/system/tts_engines/rvc/lib/utils.py b/system/tts_engines/rvc/lib/utils.py new file mode 100644 index 0000000..a9350e1 --- /dev/null +++ b/system/tts_engines/rvc/lib/utils.py @@ -0,0 +1,87 @@ +import numpy as np +import re +import unicodedata +from fairseq import checkpoint_utils + +import logging + +logging.getLogger("fairseq").setLevel(logging.WARNING) +import sys +import os +import numpy as np +import asyncio +from ffmpeg.asyncio import FFmpeg +from pathlib import Path +import subprocess + +# Get the parent directory of the current file +this_dir = Path(__file__).resolve().parent.parent.parent.parent.parent + +def load_audio(file, sampling_rate): + try: + # Get the parent directory of the current file + this_dir = Path(__file__).resolve().parent.parent.parent.parent.parent + file = file.strip(" ").strip('"').strip("\n").strip('"').strip(" ") + + # Set the ffmpeg_path variable based on the operating system + if sys.platform == "win32": + ffmpeg_path = os.path.join(this_dir, "system", "win_ffmpeg", "ffmpeg.exe") + else: + ffmpeg_path = "ffmpeg" # Default path for Linux and macOS + + # Initialize the process variable + process = subprocess.Popen( + [ffmpeg_path, "-y", "-i", file, "-f", "f32le", "-acodec", "pcm_f32le", "-ac", "1", "-ar", str(sampling_rate), "pipe:1"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + + out, err = process.communicate() + if process.returncode != 0: + print(f"FFmpeg error: {err.decode('utf-8')}") # Debug statement + raise RuntimeError(f"FFmpeg error: {err.decode('utf-8')}") + #print("Audio loaded successfully") # Debug statement + except Exception as error: + print(f"Error loading audio: {error}") # Debug statement + raise RuntimeError(f"Failed to load audio: {error}") + + return np.frombuffer(out, np.float32).flatten() + + + +def format_title(title): + formatted_title = ( + unicodedata.normalize("NFKD", title).encode("ascii", "ignore").decode("utf-8") + ) + formatted_title = re.sub(r"[\u2500-\u257F]+", "", formatted_title) + formatted_title = re.sub(r"[^\w\s.-]", "", formatted_title) + formatted_title = re.sub(r"\s+", "_", formatted_title) + return formatted_title + + +def load_embedding(embedder_model): + #print("EMBEDDER MODEL IS", embedder_model) + embedding_list = { + "contentvec": "contentvec_base.pt", + "hubert": "hubert_base.pt", + } + + try: + this_dir = Path(__file__).resolve().parent.parent.parent.parent.parent + model_path = this_dir / "models" / "rvc_base" / embedding_list[embedder_model] + #print("MODEL PATH IS", model_path) + + # Load model ensemble and task + models = checkpoint_utils.load_model_ensemble_and_task( + [f"{model_path}"], + suffix="", + ) + + #print(f"Embedding model {embedder_model} loaded successfully.") + return models + except KeyError as e: + logging.error(f"Invalid embedder model name: {embedder_model}") + raise ValueError(f"Invalid embedder model name: {embedder_model}") from e + except Exception as e: + logging.error(f"Error loading embedding model: {e}") + raise RuntimeError(f"Error loading embedding model: {e}") from e diff --git a/system/tts_engines/rvc/pretraineds/.gitignore b/system/tts_engines/rvc/pretraineds/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/system/tts_engines/rvc/pretraineds/pretrained_v1/.gitignore b/system/tts_engines/rvc/pretraineds/pretrained_v1/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/system/tts_engines/rvc/pretraineds/pretrained_v2/.gitignore b/system/tts_engines/rvc/pretraineds/pretrained_v2/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/system/tts_engines/rvc/train/data_utils.py b/system/tts_engines/rvc/train/data_utils.py new file mode 100644 index 0000000..8d18975 --- /dev/null +++ b/system/tts_engines/rvc/train/data_utils.py @@ -0,0 +1,452 @@ +import os +import numpy as np +import torch +import torch.utils.data + +from mel_processing import spectrogram_torch +from utils import load_filepaths_and_text, load_wav_to_torch + + +class TextAudioLoaderMultiNSFsid(torch.utils.data.Dataset): + def __init__(self, hparams): + self.audiopaths_and_text = load_filepaths_and_text(hparams.training_files) + self.max_wav_value = hparams.max_wav_value + self.sampling_rate = hparams.sampling_rate + self.filter_length = hparams.filter_length + self.hop_length = hparams.hop_length + self.win_length = hparams.win_length + self.sampling_rate = hparams.sampling_rate + self.min_text_len = getattr(hparams, "min_text_len", 1) + self.max_text_len = getattr(hparams, "max_text_len", 5000) + self._filter() + + def _filter(self): + audiopaths_and_text_new = [] + lengths = [] + for audiopath, text, pitch, pitchf, dv in self.audiopaths_and_text: + if self.min_text_len <= len(text) and len(text) <= self.max_text_len: + audiopaths_and_text_new.append([audiopath, text, pitch, pitchf, dv]) + lengths.append(os.path.getsize(audiopath) // (3 * self.hop_length)) + self.audiopaths_and_text = audiopaths_and_text_new + self.lengths = lengths + + def get_sid(self, sid): + sid = torch.LongTensor([int(sid)]) + return sid + + def get_audio_text_pair(self, audiopath_and_text): + file = audiopath_and_text[0] + phone = audiopath_and_text[1] + pitch = audiopath_and_text[2] + pitchf = audiopath_and_text[3] + dv = audiopath_and_text[4] + + phone, pitch, pitchf = self.get_labels(phone, pitch, pitchf) + spec, wav = self.get_audio(file) + dv = self.get_sid(dv) + + len_phone = phone.size()[0] + len_spec = spec.size()[-1] + if len_phone != len_spec: + len_min = min(len_phone, len_spec) + len_wav = len_min * self.hop_length + + spec = spec[:, :len_min] + wav = wav[:, :len_wav] + + phone = phone[:len_min, :] + pitch = pitch[:len_min] + pitchf = pitchf[:len_min] + + return (spec, wav, phone, pitch, pitchf, dv) + + def get_labels(self, phone, pitch, pitchf): + phone = np.load(phone) + phone = np.repeat(phone, 2, axis=0) + pitch = np.load(pitch) + pitchf = np.load(pitchf) + n_num = min(phone.shape[0], 900) + phone = phone[:n_num, :] + pitch = pitch[:n_num] + pitchf = pitchf[:n_num] + phone = torch.FloatTensor(phone) + pitch = torch.LongTensor(pitch) + pitchf = torch.FloatTensor(pitchf) + return phone, pitch, pitchf + + def get_audio(self, filename): + audio, sampling_rate = load_wav_to_torch(filename) + if sampling_rate != self.sampling_rate: + raise ValueError( + "{} SR doesn't match target {} SR".format( + sampling_rate, self.sampling_rate + ) + ) + audio_norm = audio + audio_norm = audio_norm.unsqueeze(0) + spec_filename = filename.replace(".wav", ".spec.pt") + if os.path.exists(spec_filename): + try: + spec = torch.load(spec_filename) + except Exception as error: + print(f"{spec_filename}: {error}") + spec = spectrogram_torch( + audio_norm, + self.filter_length, + self.hop_length, + self.win_length, + center=False, + ) + spec = torch.squeeze(spec, 0) + torch.save(spec, spec_filename, _use_new_zipfile_serialization=False) + else: + spec = spectrogram_torch( + audio_norm, + self.filter_length, + self.hop_length, + self.win_length, + center=False, + ) + spec = torch.squeeze(spec, 0) + torch.save(spec, spec_filename, _use_new_zipfile_serialization=False) + return spec, audio_norm + + def __getitem__(self, index): + return self.get_audio_text_pair(self.audiopaths_and_text[index]) + + def __len__(self): + return len(self.audiopaths_and_text) + + +class TextAudioCollateMultiNSFsid: + def __init__(self, return_ids=False): + self.return_ids = return_ids + + def __call__(self, batch): + _, ids_sorted_decreasing = torch.sort( + torch.LongTensor([x[0].size(1) for x in batch]), dim=0, descending=True + ) + + max_spec_len = max([x[0].size(1) for x in batch]) + max_wave_len = max([x[1].size(1) for x in batch]) + spec_lengths = torch.LongTensor(len(batch)) + wave_lengths = torch.LongTensor(len(batch)) + spec_padded = torch.FloatTensor(len(batch), batch[0][0].size(0), max_spec_len) + wave_padded = torch.FloatTensor(len(batch), 1, max_wave_len) + spec_padded.zero_() + wave_padded.zero_() + + max_phone_len = max([x[2].size(0) for x in batch]) + phone_lengths = torch.LongTensor(len(batch)) + phone_padded = torch.FloatTensor( + len(batch), max_phone_len, batch[0][2].shape[1] + ) + pitch_padded = torch.LongTensor(len(batch), max_phone_len) + pitchf_padded = torch.FloatTensor(len(batch), max_phone_len) + phone_padded.zero_() + pitch_padded.zero_() + pitchf_padded.zero_() + sid = torch.LongTensor(len(batch)) + + for i in range(len(ids_sorted_decreasing)): + row = batch[ids_sorted_decreasing[i]] + + spec = row[0] + spec_padded[i, :, : spec.size(1)] = spec + spec_lengths[i] = spec.size(1) + + wave = row[1] + wave_padded[i, :, : wave.size(1)] = wave + wave_lengths[i] = wave.size(1) + + phone = row[2] + phone_padded[i, : phone.size(0), :] = phone + phone_lengths[i] = phone.size(0) + + pitch = row[3] + pitch_padded[i, : pitch.size(0)] = pitch + pitchf = row[4] + pitchf_padded[i, : pitchf.size(0)] = pitchf + + sid[i] = row[5] + + return ( + phone_padded, + phone_lengths, + pitch_padded, + pitchf_padded, + spec_padded, + spec_lengths, + wave_padded, + wave_lengths, + sid, + ) + + +class TextAudioLoader(torch.utils.data.Dataset): + def __init__(self, hparams): + self.audiopaths_and_text = load_filepaths_and_text(hparams.training_files) + self.max_wav_value = hparams.max_wav_value + self.sampling_rate = hparams.sampling_rate + self.filter_length = hparams.filter_length + self.hop_length = hparams.hop_length + self.win_length = hparams.win_length + self.sampling_rate = hparams.sampling_rate + self.min_text_len = getattr(hparams, "min_text_len", 1) + self.max_text_len = getattr(hparams, "max_text_len", 5000) + self._filter() + + def _filter(self): + audiopaths_and_text_new = [] + lengths = [] + for entry in self.audiopaths_and_text: + if len(entry) >= 3: + audiopath, text, dv = entry[:3] + if self.min_text_len <= len(text) and len(text) <= self.max_text_len: + audiopaths_and_text_new.append([audiopath, text, dv]) + lengths.append(os.path.getsize(audiopath) // (3 * self.hop_length)) + + self.audiopaths_and_text = audiopaths_and_text_new + self.lengths = lengths + + def get_sid(self, sid): + sid = os.path.basename(os.path.dirname(sid)) + + try: + sid = torch.LongTensor([int("".join(filter(str.isdigit, sid)))]) + except ValueError as error: + print(f"Error converting speaker ID '{sid}' to integer. Exception: {error}") + sid = torch.LongTensor([0]) + + return sid + + def get_audio_text_pair(self, audiopath_and_text): + file = audiopath_and_text[0] + phone = audiopath_and_text[1] + dv = audiopath_and_text[2] + + phone = self.get_labels(phone) + spec, wav = self.get_audio(file) + dv = self.get_sid(dv) + + len_phone = phone.size()[0] + len_spec = spec.size()[-1] + if len_phone != len_spec: + len_min = min(len_phone, len_spec) + len_wav = len_min * self.hop_length + spec = spec[:, :len_min] + wav = wav[:, :len_wav] + phone = phone[:len_min, :] + return (spec, wav, phone, dv) + + def get_labels(self, phone): + phone = np.load(phone) + phone = np.repeat(phone, 2, axis=0) + n_num = min(phone.shape[0], 900) + phone = phone[:n_num, :] + phone = torch.FloatTensor(phone) + return phone + + def get_audio(self, filename): + audio, sampling_rate = load_wav_to_torch(filename) + if sampling_rate != self.sampling_rate: + raise ValueError( + "{} SR doesn't match target {} SR".format( + sampling_rate, self.sampling_rate + ) + ) + audio_norm = audio + audio_norm = audio_norm.unsqueeze(0) + spec_filename = filename.replace(".wav", ".spec.pt") + if os.path.exists(spec_filename): + try: + spec = torch.load(spec_filename) + except Exception as error: + print(f"{spec_filename}: {error}") + spec = spectrogram_torch( + audio_norm, + self.filter_length, + self.hop_length, + self.win_length, + center=False, + ) + spec = torch.squeeze(spec, 0) + torch.save(spec, spec_filename, _use_new_zipfile_serialization=False) + else: + spec = spectrogram_torch( + audio_norm, + self.filter_length, + self.hop_length, + self.win_length, + center=False, + ) + spec = torch.squeeze(spec, 0) + torch.save(spec, spec_filename, _use_new_zipfile_serialization=False) + return spec, audio_norm + + def __getitem__(self, index): + return self.get_audio_text_pair(self.audiopaths_and_text[index]) + + def __len__(self): + return len(self.audiopaths_and_text) + + +class TextAudioCollate: + def __init__(self, return_ids=False): + self.return_ids = return_ids + + def __call__(self, batch): + _, ids_sorted_decreasing = torch.sort( + torch.LongTensor([x[0].size(1) for x in batch]), dim=0, descending=True + ) + + max_spec_len = max([x[0].size(1) for x in batch]) + max_wave_len = max([x[1].size(1) for x in batch]) + spec_lengths = torch.LongTensor(len(batch)) + wave_lengths = torch.LongTensor(len(batch)) + spec_padded = torch.FloatTensor(len(batch), batch[0][0].size(0), max_spec_len) + wave_padded = torch.FloatTensor(len(batch), 1, max_wave_len) + spec_padded.zero_() + wave_padded.zero_() + + max_phone_len = max([x[2].size(0) for x in batch]) + phone_lengths = torch.LongTensor(len(batch)) + phone_padded = torch.FloatTensor( + len(batch), max_phone_len, batch[0][2].shape[1] + ) + phone_padded.zero_() + sid = torch.LongTensor(len(batch)) + + for i in range(len(ids_sorted_decreasing)): + row = batch[ids_sorted_decreasing[i]] + + spec = row[0] + spec_padded[i, :, : spec.size(1)] = spec + spec_lengths[i] = spec.size(1) + + wave = row[1] + wave_padded[i, :, : wave.size(1)] = wave + wave_lengths[i] = wave.size(1) + + phone = row[2] + phone_padded[i, : phone.size(0), :] = phone + phone_lengths[i] = phone.size(0) + + sid[i] = row[3] + + return ( + phone_padded, + phone_lengths, + spec_padded, + spec_lengths, + wave_padded, + wave_lengths, + sid, + ) + + +class DistributedBucketSampler(torch.utils.data.distributed.DistributedSampler): + def __init__( + self, + dataset, + batch_size, + boundaries, + num_replicas=None, + rank=None, + shuffle=True, + ): + super().__init__(dataset, num_replicas=num_replicas, rank=rank, shuffle=shuffle) + self.lengths = dataset.lengths + self.batch_size = batch_size + self.boundaries = boundaries + + self.buckets, self.num_samples_per_bucket = self._create_buckets() + self.total_size = sum(self.num_samples_per_bucket) + self.num_samples = self.total_size // self.num_replicas + + def _create_buckets(self): + buckets = [[] for _ in range(len(self.boundaries) - 1)] + for i in range(len(self.lengths)): + length = self.lengths[i] + idx_bucket = self._bisect(length) + if idx_bucket != -1: + buckets[idx_bucket].append(i) + + for i in range(len(buckets) - 1, -1, -1): # + if len(buckets[i]) == 0: + buckets.pop(i) + self.boundaries.pop(i + 1) + + num_samples_per_bucket = [] + for i in range(len(buckets)): + len_bucket = len(buckets[i]) + total_batch_size = self.num_replicas * self.batch_size + rem = ( + total_batch_size - (len_bucket % total_batch_size) + ) % total_batch_size + num_samples_per_bucket.append(len_bucket + rem) + return buckets, num_samples_per_bucket + + def __iter__(self): + g = torch.Generator() + g.manual_seed(self.epoch) + + indices = [] + if self.shuffle: + for bucket in self.buckets: + indices.append(torch.randperm(len(bucket), generator=g).tolist()) + else: + for bucket in self.buckets: + indices.append(list(range(len(bucket)))) + + batches = [] + for i in range(len(self.buckets)): + bucket = self.buckets[i] + len_bucket = len(bucket) + ids_bucket = indices[i] + num_samples_bucket = self.num_samples_per_bucket[i] + + rem = num_samples_bucket - len_bucket + ids_bucket = ( + ids_bucket + + ids_bucket * (rem // len_bucket) + + ids_bucket[: (rem % len_bucket)] + ) + + ids_bucket = ids_bucket[self.rank :: self.num_replicas] + + # batching + for j in range(len(ids_bucket) // self.batch_size): + batch = [ + bucket[idx] + for idx in ids_bucket[ + j * self.batch_size : (j + 1) * self.batch_size + ] + ] + batches.append(batch) + + if self.shuffle: + batch_ids = torch.randperm(len(batches), generator=g).tolist() + batches = [batches[i] for i in batch_ids] + self.batches = batches + + assert len(self.batches) * self.batch_size == self.num_samples + return iter(self.batches) + + def _bisect(self, x, lo=0, hi=None): + if hi is None: + hi = len(self.boundaries) - 1 + + if hi > lo: + mid = (hi + lo) // 2 + if self.boundaries[mid] < x and x <= self.boundaries[mid + 1]: + return mid + elif x <= self.boundaries[mid]: + return self._bisect(x, lo, mid) + else: + return self._bisect(x, mid + 1, hi) + else: + return -1 + + def __len__(self): + return self.num_samples // self.batch_size diff --git a/system/tts_engines/rvc/train/extract/extract_f0_print.py b/system/tts_engines/rvc/train/extract/extract_f0_print.py new file mode 100644 index 0000000..f62fb5d --- /dev/null +++ b/system/tts_engines/rvc/train/extract/extract_f0_print.py @@ -0,0 +1,237 @@ +import os +import sys +import numpy as np +import pyworld +import torchcrepe +import torch +import parselmouth +import tqdm +from multiprocessing import Process, cpu_count + +current_directory = os.getcwd() +sys.path.append(current_directory) + + +from rvc.lib.utils import load_audio + + +exp_dir = sys.argv[1] +f0_method = sys.argv[2] +num_processes = cpu_count() + +try: + hop_length = int(sys.argv[3]) +except ValueError: + hop_length = 128 + +DoFormant = False +Quefrency = 1.0 +Timbre = 1.0 + + +class FeatureInput: + def __init__(self, sample_rate=16000, hop_size=160): + self.fs = sample_rate + self.hop = hop_size + + self.f0_method_dict = self.get_f0_method_dict() + + self.f0_bin = 256 + self.f0_max = 1100.0 + self.f0_min = 50.0 + self.f0_mel_min = 1127 * np.log(1 + self.f0_min / 700) + self.f0_mel_max = 1127 * np.log(1 + self.f0_max / 700) + + def mncrepe(self, method, x, p_len, hop_length): + f0 = None + torch_device_index = 0 + torch_device = ( + torch.device(f"cuda:{torch_device_index % torch.cuda.device_count()}") + if torch.cuda.is_available() + else ( + torch.device("mps") + if torch.backends.mps.is_available() + else torch.device("cpu") + ) + ) + + audio = torch.from_numpy(x.astype(np.float32)).to(torch_device, copy=True) + audio /= torch.quantile(torch.abs(audio), 0.999) + audio = torch.unsqueeze(audio, dim=0) + if audio.ndim == 2 and audio.shape[0] > 1: + audio = torch.mean(audio, dim=0, keepdim=True).detach() + audio = audio.detach() + + if method == "crepe": + pitch = torchcrepe.predict( + audio, + self.fs, + hop_length, + self.f0_min, + self.f0_max, + "full", + batch_size=hop_length * 2, + device=torch_device, + pad=True, + ) + p_len = p_len or x.shape[0] // hop_length + source = np.array(pitch.squeeze(0).cpu().float().numpy()) + source[source < 0.001] = np.nan + target = np.interp( + np.arange(0, len(source) * p_len, len(source)) / p_len, + np.arange(0, len(source)), + source, + ) + f0 = np.nan_to_num(target) + + return f0 + + def get_pm(self, x, p_len): + f0 = ( + parselmouth.Sound(x, self.fs) + .to_pitch_ac( + time_step=160 / 16000, + voicing_threshold=0.6, + pitch_floor=self.f0_min, + pitch_ceiling=self.f0_max, + ) + .selected_array["frequency"] + ) + + return np.pad( + f0, + [ + [ + max(0, (p_len - len(f0) + 1) // 2), + max(0, p_len - len(f0) - (p_len - len(f0) + 1) // 2), + ] + ], + mode="constant", + ) + + def get_harvest(self, x): + f0_spectral = pyworld.harvest( + x.astype(np.double), + fs=self.fs, + f0_ceil=self.f0_max, + f0_floor=self.f0_min, + frame_period=1000 * self.hop / self.fs, + ) + return pyworld.stonemask(x.astype(np.double), *f0_spectral, self.fs) + + def get_dio(self, x): + f0_spectral = pyworld.dio( + x.astype(np.double), + fs=self.fs, + f0_ceil=self.f0_max, + f0_floor=self.f0_min, + frame_period=1000 * self.hop / self.fs, + ) + return pyworld.stonemask(x.astype(np.double), *f0_spectral, self.fs) + + def get_rmvpe(self, x): + if not hasattr(self, "model_rmvpe"): + from rvc.lib.rmvpe import RMVPE + + self.model_rmvpe = RMVPE(current_directory / "models" / "rvc_base" / "rmvpe.pt", is_half=False, device="cpu") + return self.model_rmvpe.infer_from_audio(x, thred=0.03) + + def get_f0_method_dict(self): + return { + "pm": self.get_pm, + "harvest": self.get_harvest, + "dio": self.get_dio, + "rmvpe": self.get_rmvpe, + } + + def compute_f0(self, path, f0_method, hop_length): + x = load_audio(path, self.fs) + p_len = x.shape[0] // self.hop + + if f0_method in self.f0_method_dict: + f0 = ( + self.f0_method_dict[f0_method](x, p_len) + if f0_method == "pm" + else self.f0_method_dict[f0_method](x) + ) + elif f0_method == "crepe": + f0 = self.mncrepe(f0_method, x, p_len, hop_length) + return f0 + + def coarse_f0(self, f0): + f0_mel = 1127 * np.log(1 + f0 / 700) + f0_mel[f0_mel > 0] = (f0_mel[f0_mel > 0] - self.f0_mel_min) * ( + self.f0_bin - 2 + ) / (self.f0_mel_max - self.f0_mel_min) + 1 + + # use 0 or 1 + f0_mel[f0_mel <= 1] = 1 + f0_mel[f0_mel > self.f0_bin - 1] = self.f0_bin - 1 + f0_coarse = np.rint(f0_mel).astype(int) + assert f0_coarse.max() <= 255 and f0_coarse.min() >= 1, ( + f0_coarse.max(), + f0_coarse.min(), + ) + return f0_coarse + + def process_paths(self, paths, f0_method, hop_length, thread_n): + if len(paths) == 0: + print("There are no paths to process.") + return + with tqdm.tqdm(total=len(paths), leave=True, position=thread_n) as pbar: + description = f"Thread {thread_n} | Hop-Length {hop_length}" + pbar.set_description(description) + + for idx, (inp_path, opt_path1, opt_path2) in enumerate(paths): + try: + if os.path.exists(opt_path1 + ".npy") and os.path.exists( + opt_path2 + ".npy" + ): + pbar.update(1) + continue + + feature_pit = self.compute_f0(inp_path, f0_method, hop_length) + np.save( + opt_path2, + feature_pit, + allow_pickle=False, + ) # nsf + coarse_pit = self.coarse_f0(feature_pit) + np.save( + opt_path1, + coarse_pit, + allow_pickle=False, + ) # ori + pbar.update(1) + except Exception as error: + print(f"f0fail-{idx}-{inp_path}-{error}") + + +if __name__ == "__main__": + feature_input = FeatureInput() + paths = [] + input_root = f"{exp_dir}/1_16k_wavs" + output_root1 = f"{exp_dir}/2a_f0" + output_root2 = f"{exp_dir}/2b-f0nsf" + + os.makedirs(output_root1, exist_ok=True) + os.makedirs(output_root2, exist_ok=True) + for name in sorted(list(os.listdir(input_root))): + input_path = f"{input_root}/{name}" + if "spec" in input_path: + continue + output_path1 = f"{output_root1}/{name}" + output_path2 = f"{output_root2}/{name}" + paths.append([input_path, output_path1, output_path2]) + + processes = [] + print("Using f0 method: " + f0_method) + for i in range(num_processes): + p = Process( + target=feature_input.process_paths, + args=(paths[i::num_processes], f0_method, hop_length, i), + ) + processes.append(p) + p.start() + for i in range(num_processes): + processes[i].join() diff --git a/system/tts_engines/rvc/train/extract/extract_feature_print.py b/system/tts_engines/rvc/train/extract/extract_feature_print.py new file mode 100644 index 0000000..d78d86a --- /dev/null +++ b/system/tts_engines/rvc/train/extract/extract_feature_print.py @@ -0,0 +1,94 @@ +import os +import sys +import tqdm +import torch +import torch.nn.functional as F +import soundfile as sf +import numpy as np + +now_dir = os.getcwd() +sys.path.append(now_dir) +from rvc.lib.utils import load_embedding + +device = sys.argv[1] +n_parts = int(sys.argv[2]) +i_part = int(sys.argv[3]) +i_gpu = sys.argv[4] +exp_dir = sys.argv[5] +os.environ["CUDA_VISIBLE_DEVICES"] = str(i_gpu) +version = sys.argv[6] +is_half = bool(sys.argv[7]) +embedder_model = sys.argv[8] + + +wav_path = f"{exp_dir}/1_16k_wavs" +out_path = f"{exp_dir}/3_feature256" if version == "v1" else f"{exp_dir}/3_feature768" +os.makedirs(out_path, exist_ok=True) + + +def read_wave(wav_path, normalize=False): + wav, sr = sf.read(wav_path) + assert sr == 16000 + feats = torch.from_numpy(wav) + feats = feats.half() if is_half else feats.float() + feats = feats.mean(-1) if feats.dim() == 2 else feats + feats = feats.view(1, -1) + if normalize: + with torch.no_grad(): + feats = F.layer_norm(feats, feats.shape) + return feats + + +print("Starting feature extraction...") +models, saved_cfg, task = load_embedding(embedder_model) +model = models[0] +model = model.to(device) +if device not in ["mps", "cpu"]: + model = model.half() +model.eval() + +todo = sorted(os.listdir(wav_path))[i_part::n_parts] +n = max(1, len(todo) // 10) + +if len(todo) == 0: + print( + "An error occurred in the feature extraction, make sure you have provided the audios correctly." + ) +else: + print(f"{len(todo)}") + with tqdm.tqdm(total=len(todo)) as pbar: + for idx, file in enumerate(todo): + try: + if file.endswith(".wav"): + wav_file_path = os.path.join(wav_path, file) + out_file_path = os.path.join(out_path, file.replace("wav", "npy")) + + if os.path.exists(out_file_path): + continue + + feats = read_wave(wav_file_path, normalize=saved_cfg.task.normalize) + padding_mask = torch.BoolTensor(feats.shape).fill_(False) + inputs = { + "source": feats.to(device), + "padding_mask": padding_mask.to(device), + "output_layer": 9 if version == "v1" else 12, + } + with torch.no_grad(): + logits = model.extract_features(**inputs) + feats = ( + model.final_proj(logits[0]) + if version == "v1" + else logits[0] + ) + + feats = feats.squeeze(0).float().cpu().numpy() + if np.isnan(feats).sum() == 0: + np.save(out_file_path, feats, allow_pickle=False) + else: + print(f"{file} - contains nan") + pbar.set_description(f"Processing {file} {feats.shape}") + except Exception as error: + print(error) + pbar.update(1) + + print("Feature extraction completed successfully!") diff --git a/system/tts_engines/rvc/train/extract/preparing_files.py b/system/tts_engines/rvc/train/extract/preparing_files.py new file mode 100644 index 0000000..73bf681 --- /dev/null +++ b/system/tts_engines/rvc/train/extract/preparing_files.py @@ -0,0 +1,71 @@ +import os +import json +import pathlib +from random import shuffle + +from rvc.configs.config import Config + +config = Config() +current_directory = os.getcwd() + + +def generate_config(rvc_version, sampling_rate, model_path): + if rvc_version == "v1" or sampling_rate == "40000": + config_path = f"v1/{sampling_rate}.json" + else: + config_path = f"v2/{sampling_rate}.json" + config_save_path = os.path.join(model_path, "config.json") + if not pathlib.Path(config_save_path).exists(): + with open(config_save_path, "w", encoding="utf-8") as f: + json.dump( + config.json_config[config_path], + f, + ensure_ascii=False, + indent=4, + sort_keys=True, + ) + f.write("\n") + + +def generate_filelist(f0_method, model_path, rvc_version, sampling_rate): + gt_wavs_dir = f"{model_path}/0_gt_wavs" + feature_dir = ( + f"{model_path}/3_feature256" + if rvc_version == "v1" + else f"{model_path}/3_feature768" + ) + if f0_method: + f0_dir = f"{model_path}/2a_f0" + f0nsf_dir = f"{model_path}/2b-f0nsf" + names = ( + set([name.split(".")[0] for name in os.listdir(gt_wavs_dir)]) + & set([name.split(".")[0] for name in os.listdir(feature_dir)]) + & set([name.split(".")[0] for name in os.listdir(f0_dir)]) + & set([name.split(".")[0] for name in os.listdir(f0nsf_dir)]) + ) + else: + names = set([name.split(".")[0] for name in os.listdir(gt_wavs_dir)]) & set( + [name.split(".")[0] for name in os.listdir(feature_dir)] + ) + options = [] + for name in names: + if f0_method: + options.append( + f"{gt_wavs_dir}/{name}.wav|{feature_dir}/{name}.npy|{f0_dir}/{name}.wav.npy|{f0nsf_dir}/{name}.wav.npy|0" + ) + else: + options.append(f"{gt_wavs_dir}/{name}.wav|{feature_dir}/{name}.npy|0") + fea_dim = 256 if rvc_version == "v1" else 768 + if f0_method: + for _ in range(2): + options.append( + f"{current_directory}/logs/mute/0_gt_wavs/mute{sampling_rate}.wav|{current_directory}/logs/mute/3_feature{fea_dim}/mute.npy|{current_directory}/logs/mute/2a_f0/mute.wav.npy|{current_directory}/logs/mute/2b-f0nsf/mute.wav.npy|0" + ) + else: + for _ in range(2): + options.append( + f"{current_directory}/logs/mute/0_gt_wavs/mute{sampling_rate}.wav|{current_directory}/logs/mute/3_feature{fea_dim}/mute.npy|0" + ) + shuffle(options) + with open(f"{model_path}/filelist.txt", "w") as f: + f.write("\n".join(options)) diff --git a/system/tts_engines/rvc/train/losses.py b/system/tts_engines/rvc/train/losses.py new file mode 100644 index 0000000..b1b263e --- /dev/null +++ b/system/tts_engines/rvc/train/losses.py @@ -0,0 +1,58 @@ +import torch + + +def feature_loss(fmap_r, fmap_g): + loss = 0 + for dr, dg in zip(fmap_r, fmap_g): + for rl, gl in zip(dr, dg): + rl = rl.float().detach() + gl = gl.float() + loss += torch.mean(torch.abs(rl - gl)) + + return loss * 2 + + +def discriminator_loss(disc_real_outputs, disc_generated_outputs): + loss = 0 + r_losses = [] + g_losses = [] + for dr, dg in zip(disc_real_outputs, disc_generated_outputs): + dr = dr.float() + dg = dg.float() + r_loss = torch.mean((1 - dr) ** 2) + g_loss = torch.mean(dg**2) + loss += r_loss + g_loss + r_losses.append(r_loss.item()) + g_losses.append(g_loss.item()) + + return loss, r_losses, g_losses + + +def generator_loss(disc_outputs): + loss = 0 + gen_losses = [] + for dg in disc_outputs: + dg = dg.float() + l = torch.mean((1 - dg) ** 2) + gen_losses.append(l) + loss += l + + return loss, gen_losses + + +def kl_loss(z_p, logs_q, m_p, logs_p, z_mask): + """ + z_p, logs_q: [b, h, t_t] + m_p, logs_p: [b, h, t_t] + """ + z_p = z_p.float() + logs_q = logs_q.float() + m_p = m_p.float() + logs_p = logs_p.float() + z_mask = z_mask.float() + + kl = logs_p - logs_q - 0.5 + kl += 0.5 * ((z_p - m_p) ** 2) * torch.exp(-2.0 * logs_p) + kl = torch.sum(kl * z_mask) + l = kl / torch.sum(z_mask) + return l diff --git a/system/tts_engines/rvc/train/mel_processing.py b/system/tts_engines/rvc/train/mel_processing.py new file mode 100644 index 0000000..b664712 --- /dev/null +++ b/system/tts_engines/rvc/train/mel_processing.py @@ -0,0 +1,84 @@ +import torch +import torch.utils.data +from librosa.filters import mel as librosa_mel_fn + + +def dynamic_range_compression_torch(x, C=1, clip_val=1e-5): + return torch.log(torch.clamp(x, min=clip_val) * C) + + +def dynamic_range_decompression_torch(x, C=1): + return torch.exp(x) / C + + +def spectral_normalize_torch(magnitudes): + return dynamic_range_compression_torch(magnitudes) + + +def spectral_de_normalize_torch(magnitudes): + return dynamic_range_decompression_torch(magnitudes) + + +mel_basis = {} +hann_window = {} + + +def spectrogram_torch(y, n_fft, hop_size, win_size, center=False): + global hann_window + dtype_device = str(y.dtype) + "_" + str(y.device) + wnsize_dtype_device = str(win_size) + "_" + dtype_device + if wnsize_dtype_device not in hann_window: + hann_window[wnsize_dtype_device] = torch.hann_window(win_size).to( + dtype=y.dtype, device=y.device + ) + + y = torch.nn.functional.pad( + y.unsqueeze(1), + (int((n_fft - hop_size) / 2), int((n_fft - hop_size) / 2)), + mode="reflect", + ) + y = y.squeeze(1) + + spec = torch.stft( + y, + n_fft, + hop_length=hop_size, + win_length=win_size, + window=hann_window[wnsize_dtype_device], + center=center, + pad_mode="reflect", + normalized=False, + onesided=True, + return_complex=True, + ) + + spec = torch.sqrt(spec.real.pow(2) + spec.imag.pow(2) + 1e-6) + + return spec + + +def spec_to_mel_torch(spec, n_fft, num_mels, sampling_rate, fmin, fmax): + global mel_basis + dtype_device = str(spec.dtype) + "_" + str(spec.device) + fmax_dtype_device = str(fmax) + "_" + dtype_device + if fmax_dtype_device not in mel_basis: + mel = librosa_mel_fn( + sr=sampling_rate, n_fft=n_fft, n_mels=num_mels, fmin=fmin, fmax=fmax + ) + mel_basis[fmax_dtype_device] = torch.from_numpy(mel).to( + dtype=spec.dtype, device=spec.device + ) + + melspec = torch.matmul(mel_basis[fmax_dtype_device], spec) + melspec = spectral_normalize_torch(melspec) + return melspec + + +def mel_spectrogram_torch( + y, n_fft, num_mels, sampling_rate, hop_size, win_size, fmin, fmax, center=False +): + spec = spectrogram_torch(y, n_fft, hop_size, win_size, center) + + melspec = spec_to_mel_torch(spec, n_fft, num_mels, sampling_rate, fmin, fmax) + + return melspec diff --git a/system/tts_engines/rvc/train/preprocess/preprocess.py b/system/tts_engines/rvc/train/preprocess/preprocess.py new file mode 100644 index 0000000..48534a8 --- /dev/null +++ b/system/tts_engines/rvc/train/preprocess/preprocess.py @@ -0,0 +1,130 @@ +from multiprocessing import cpu_count +import os +import sys + +from scipy import signal +from scipy.io import wavfile +import librosa +import numpy as np + +now_directory = os.getcwd() +sys.path.append(now_directory) + +from rvc.lib.utils import load_audio +from rvc.train.slicer import Slicer + +experiment_directory = sys.argv[1] +input_root = sys.argv[2] +sampling_rate = int(sys.argv[3]) +percentage = float(sys.argv[4]) +num_processes = cpu_count() + +import multiprocessing + + +class PreProcess: + def __init__(self, sr, exp_dir, per=3.0): + self.slicer = Slicer( + sr=sr, + threshold=-42, + min_length=1500, + min_interval=400, + hop_size=15, + max_sil_kept=500, + ) + self.sr = sr + self.b_high, self.a_high = signal.butter(N=5, Wn=48, btype="high", fs=self.sr) + self.per = per + self.overlap = 0.3 + self.tail = self.per + self.overlap + self.max_amplitude = 0.9 + self.alpha = 0.75 + self.exp_dir = exp_dir + self.gt_wavs_dir = f"{exp_dir}/0_gt_wavs" + self.wavs16k_dir = f"{exp_dir}/1_16k_wavs" + os.makedirs(self.exp_dir, exist_ok=True) + os.makedirs(self.gt_wavs_dir, exist_ok=True) + os.makedirs(self.wavs16k_dir, exist_ok=True) + + def normalize_and_write(self, tmp_audio, idx0, idx1): + tmp_max = np.abs(tmp_audio).max() + if tmp_max > 2.5: + print(f"{idx0}-{idx1}-{tmp_max}-filtered") + return + tmp_audio = (tmp_audio / tmp_max * (self.max_amplitude * self.alpha)) + ( + 1 - self.alpha + ) * tmp_audio + wavfile.write( + f"{self.gt_wavs_dir}/{idx0}_{idx1}.wav", + self.sr, + tmp_audio.astype(np.float32), + ) + tmp_audio = librosa.resample( + tmp_audio, orig_sr=self.sr, target_sr=16000 + ) # , res_type="soxr_vhq" + wavfile.write( + f"{self.wavs16k_dir}/{idx0}_{idx1}.wav", + 16000, + tmp_audio.astype(np.float32), + ) + + def process_audio(self, path, idx0): + try: + audio = load_audio(path, self.sr) + audio = signal.lfilter(self.b_high, self.a_high, audio) + + idx1 = 0 + for audio_segment in self.slicer.slice(audio): + i = 0 + while 1: + start = int(self.sr * (self.per - self.overlap) * i) + i += 1 + if len(audio_segment[start:]) > self.tail * self.sr: + tmp_audio = audio_segment[ + start : start + int(self.per * self.sr) + ] + self.normalize_and_write(tmp_audio, idx0, idx1) + idx1 += 1 + else: + tmp_audio = audio_segment[start:] + idx1 += 1 + break + self.normalize_and_write(tmp_audio, idx0, idx1) + except Exception as error: + print(f"{path}: {error}") + + def process_audio_multiprocessing(self, infos): + for path, idx0 in infos: + self.process_audio(path, idx0) + + def process_audio_multiprocessing_input_directory(self, input_root, num_processes): + try: + infos = [ + (f"{input_root}/{name}", idx) + for idx, name in enumerate(sorted(list(os.listdir(input_root)))) + ] + processes = [] + for i in range(num_processes): + p = multiprocessing.Process( + target=self.process_audio_multiprocessing, + args=(infos[i::num_processes],), + ) + processes.append(p) + p.start() + for i in range(num_processes): + processes[i].join() + except Exception as error: + print(error) + + +def preprocess_training_set(input_root, sr, num_processes, exp_dir, per): + pp = PreProcess(sr, exp_dir, per) + print("Starting preprocessing...") + pp.process_audio_multiprocessing_input_directory(input_root, num_processes) + print("Preprocessing completed!") + + +if __name__ == "__main__": + preprocess_training_set( + input_root, sampling_rate, num_processes, experiment_directory, percentage + ) diff --git a/system/tts_engines/rvc/train/process/change_info.py b/system/tts_engines/rvc/train/process/change_info.py new file mode 100644 index 0000000..20589d6 --- /dev/null +++ b/system/tts_engines/rvc/train/process/change_info.py @@ -0,0 +1,14 @@ +import os +import torch + + +def change_info(path, info, name): + try: + ckpt = torch.load(path, map_location="cpu") + ckpt["info"] = info + if name == "": + name = os.path.basename(path) + torch.save(ckpt, f"logs/{name}/{name}") + return "Success." + except Exception as error: + print(error) diff --git a/system/tts_engines/rvc/train/process/extract_index.py b/system/tts_engines/rvc/train/process/extract_index.py new file mode 100644 index 0000000..db4898f --- /dev/null +++ b/system/tts_engines/rvc/train/process/extract_index.py @@ -0,0 +1,88 @@ +import os +import sys +import faiss +import numpy as np +from sklearn.cluster import MiniBatchKMeans +from multiprocessing import cpu_count + +exp_dir = sys.argv[1] +version = sys.argv[2] + +try: + if version == "v1": + feature_dir = os.path.join(exp_dir, "3_feature256") + elif version == "v2": + feature_dir = os.path.join(exp_dir, "3_feature768") + + npys = [] + listdir_res = sorted(os.listdir(feature_dir)) + + for name in listdir_res: + file_path = os.path.join(feature_dir, name) + phone = np.load(file_path) + npys.append(phone) + + big_npy = np.concatenate(npys, axis=0) + + big_npy_idx = np.arange(big_npy.shape[0]) + np.random.shuffle(big_npy_idx) + big_npy = big_npy[big_npy_idx] + + if big_npy.shape[0] > 2e5: + big_npy = ( + MiniBatchKMeans( + n_clusters=10000, + verbose=True, + batch_size=256 * cpu_count(), + compute_labels=False, + init="random", + ) + .fit(big_npy) + .cluster_centers_ + ) + + np.save(os.path.join(exp_dir, "total_fea.npy"), big_npy) + + n_ivf = min(int(16 * np.sqrt(big_npy.shape[0])), big_npy.shape[0] // 39) + + # index_trained + index_trained = faiss.index_factory( + 256 if version == "v1" else 768, f"IVF{n_ivf},Flat" + ) + index_ivf_trained = faiss.extract_index_ivf(index_trained) + index_ivf_trained.nprobe = 1 + index_trained.train(big_npy) + + index_filename_trained = ( + f"trained_IVF{n_ivf}_Flat_nprobe_{index_ivf_trained.nprobe}_{version}.index" + ) + index_filepath_trained = os.path.join(exp_dir, index_filename_trained) + + faiss.write_index(index_trained, index_filepath_trained) + + # index_added + index_added = faiss.index_factory( + 256 if version == "v1" else 768, f"IVF{n_ivf},Flat" + ) + index_ivf_added = faiss.extract_index_ivf(index_added) + index_ivf_added.nprobe = 1 + index_added.train(big_npy) + + index_filename_added = ( + f"added_IVF{n_ivf}_Flat_nprobe_{index_ivf_added.nprobe}_{version}.index" + ) + index_filepath_added = os.path.join(exp_dir, index_filename_added) + + batch_size_add = 8192 + for i in range(0, big_npy.shape[0], batch_size_add): + index_added.add(big_npy[i : i + batch_size_add]) + + faiss.write_index(index_added, index_filepath_added) + print(f"Saved index file '{index_filepath_added}'") + +except Exception as error: + print(f"Failed to train index: {error}") + if "one array to concatenate" in str(error): + print( + "If you are running this code in a virtual environment, make sure you have enough GPU available to generate the Index file." + ) diff --git a/system/tts_engines/rvc/train/process/extract_model.py b/system/tts_engines/rvc/train/process/extract_model.py new file mode 100644 index 0000000..8ff6714 --- /dev/null +++ b/system/tts_engines/rvc/train/process/extract_model.py @@ -0,0 +1,82 @@ +import os +import torch +import hashlib +import datetime +from collections import OrderedDict + + +def replace_keys_in_dict(d, old_key_part, new_key_part): + if isinstance(d, OrderedDict): + updated_dict = OrderedDict() + else: + updated_dict = {} + for key, value in d.items(): + new_key = key.replace(old_key_part, new_key_part) + if isinstance(value, dict): + value = replace_keys_in_dict(value, old_key_part, new_key_part) + updated_dict[new_key] = value + return updated_dict + + +def extract_model(ckpt, sr, if_f0, name, model_dir, epoch, step, version, hps): + try: + print(f"Saved model '{model_dir}' (epoch {epoch} and step {step})") + pth_file = f"{name}_{epoch}e_{step}s.pth" + pth_file_old_version_path = os.path.join( + model_dir, f"{pth_file}_old_version.pth" + ) + opt = OrderedDict( + weight={ + key: value.half() for key, value in ckpt.items() if "enc_q" not in key + } + ) + opt["config"] = [ + hps.data.filter_length // 2 + 1, + 32, + hps.model.inter_channels, + hps.model.hidden_channels, + hps.model.filter_channels, + hps.model.n_heads, + hps.model.n_layers, + hps.model.kernel_size, + hps.model.p_dropout, + hps.model.resblock, + hps.model.resblock_kernel_sizes, + hps.model.resblock_dilation_sizes, + hps.model.upsample_rates, + hps.model.upsample_initial_channel, + hps.model.upsample_kernel_sizes, + hps.model.spk_embed_dim, + hps.model.gin_channels, + hps.data.sampling_rate, + ] + + opt["epoch"] = epoch + opt["step"] = step + opt["sr"] = sr + opt["f0"] = if_f0 + opt["version"] = version + opt["creation_date"] = datetime.datetime.now().isoformat() + + hash_input = f"{str(ckpt)} {epoch} {step} {datetime.datetime.now().isoformat()}" + model_hash = hashlib.sha256(hash_input.encode()).hexdigest() + opt["model_hash"] = model_hash + + torch.save(opt, model_dir) + + model = torch.load(model_dir, map_location=torch.device("cpu")) + torch.save( + replace_keys_in_dict( + replace_keys_in_dict( + model, ".parametrizations.weight.original1", ".weight_v" + ), + ".parametrizations.weight.original0", + ".weight_g", + ), + pth_file_old_version_path, + ) + os.remove(model_dir) + os.rename(pth_file_old_version_path, model_dir) + + except Exception as error: + print(error) diff --git a/system/tts_engines/rvc/train/process/extract_small_model.py b/system/tts_engines/rvc/train/process/extract_small_model.py new file mode 100644 index 0000000..cd755b9 --- /dev/null +++ b/system/tts_engines/rvc/train/process/extract_small_model.py @@ -0,0 +1,175 @@ +import os +import torch +import hashlib +import datetime +from collections import OrderedDict + + +def replace_keys_in_dict(d, old_key_part, new_key_part): + # Use OrderedDict if the original is an OrderedDict + if isinstance(d, OrderedDict): + updated_dict = OrderedDict() + else: + updated_dict = {} + for key, value in d.items(): + # Replace the key part if found + new_key = key.replace(old_key_part, new_key_part) + # If the value is a dictionary, apply the function recursively + if isinstance(value, dict): + value = replace_keys_in_dict(value, old_key_part, new_key_part) + updated_dict[new_key] = value + return updated_dict + + +def extract_small_model(path, name, sr, if_f0, version, epoch, step): + try: + ckpt = torch.load(path, map_location="cpu") + pth_file = f"{name}.pth" + pth_file_old_version_path = os.path.join("logs", f"{pth_file}_old_version.pth") + opt = OrderedDict( + weight={ + key: value.half() for key, value in ckpt.items() if "enc_q" not in key + } + ) + if "model" in ckpt: + ckpt = ckpt["model"] + opt = OrderedDict() + opt["weight"] = {} + for key in ckpt.keys(): + if "enc_q" in key: + continue + opt["weight"][key] = ckpt[key].half() + if sr == "40000": + opt["config"] = [ + 1025, + 32, + 192, + 192, + 768, + 2, + 6, + 3, + 0, + "1", + [3, 7, 11], + [[1, 3, 5], [1, 3, 5], [1, 3, 5]], + [10, 10, 2, 2], + 512, + [16, 16, 4, 4], + 109, + 256, + 40000, + ] + elif sr == "48000": + if version == "v1": + opt["config"] = [ + 1025, + 32, + 192, + 192, + 768, + 2, + 6, + 3, + 0, + "1", + [3, 7, 11], + [[1, 3, 5], [1, 3, 5], [1, 3, 5]], + [10, 6, 2, 2, 2], + 512, + [16, 16, 4, 4, 4], + 109, + 256, + 48000, + ] + else: + opt["config"] = [ + 1025, + 32, + 192, + 192, + 768, + 2, + 6, + 3, + 0, + "1", + [3, 7, 11], + [[1, 3, 5], [1, 3, 5], [1, 3, 5]], + [12, 10, 2, 2], + 512, + [24, 20, 4, 4], + 109, + 256, + 48000, + ] + elif sr == "32000": + if version == "v1": + opt["config"] = [ + 513, + 32, + 192, + 192, + 768, + 2, + 6, + 3, + 0, + "1", + [3, 7, 11], + [[1, 3, 5], [1, 3, 5], [1, 3, 5]], + [10, 4, 2, 2, 2], + 512, + [16, 16, 4, 4, 4], + 109, + 256, + 32000, + ] + else: + opt["config"] = [ + 513, + 32, + 192, + 192, + 768, + 2, + 6, + 3, + 0, + "1", + [3, 7, 11], + [[1, 3, 5], [1, 3, 5], [1, 3, 5]], + [10, 8, 2, 2], + 512, + [20, 16, 4, 4], + 109, + 256, + 32000, + ] + + opt["epoch"] = epoch + opt["step"] = step + opt["sr"] = sr + opt["f0"] = int(if_f0) + opt["version"] = version + opt["creation_date"] = datetime.datetime.now().isoformat() + + hash_input = f"{str(ckpt)} {epoch} {step} {datetime.datetime.now().isoformat()}" + model_hash = hashlib.sha256(hash_input.encode()).hexdigest() + opt["model_hash"] = model_hash + + model = torch.load(pth_file_old_version_path, map_location=torch.device("cpu")) + torch.save( + replace_keys_in_dict( + replace_keys_in_dict( + model, ".parametrizations.weight.original1", ".weight_v" + ), + ".parametrizations.weight.original0", + ".weight_g", + ), + pth_file_old_version_path, + ) + os.remove(pth_file_old_version_path) + os.rename(pth_file_old_version_path, pth_file) + except Exception as error: + print(error) diff --git a/system/tts_engines/rvc/train/process/model_blender.py b/system/tts_engines/rvc/train/process/model_blender.py new file mode 100644 index 0000000..71051e5 --- /dev/null +++ b/system/tts_engines/rvc/train/process/model_blender.py @@ -0,0 +1,63 @@ +import os +import torch +from collections import OrderedDict + + +def extract(ckpt): + a = ckpt["model"] + opt = OrderedDict() + opt["weight"] = {} + for key in a.keys(): + if "enc_q" in key: + continue + opt["weight"][key] = a[key] + return opt + + +def model_blender(name, path1, path2, ratio): + try: + message = f"Model {path1} and {path2} are merged with alpha {ratio}." + ckpt1 = torch.load(path1, map_location="cpu") + ckpt2 = torch.load(path2, map_location="cpu") + cfg = ckpt1["config"] + cfg_f0 = ckpt1["f0"] + cfg_version = ckpt1["version"] + + if "model" in ckpt1: + ckpt1 = extract(ckpt1) + else: + ckpt1 = ckpt1["weight"] + if "model" in ckpt2: + ckpt2 = extract(ckpt2) + else: + ckpt2 = ckpt2["weight"] + + if sorted(list(ckpt1.keys())) != sorted(list(ckpt2.keys())): + return "Fail to merge the models. The model architectures are not the same." + + opt = OrderedDict() + opt["weight"] = {} + for key in ckpt1.keys(): + if key == "emb_g.weight" and ckpt1[key].shape != ckpt2[key].shape: + min_shape0 = min(ckpt1[key].shape[0], ckpt2[key].shape[0]) + opt["weight"][key] = ( + ratio * (ckpt1[key][:min_shape0].float()) + + (1 - ratio) * (ckpt2[key][:min_shape0].float()) + ).half() + else: + opt["weight"][key] = ( + ratio * (ckpt1[key].float()) + (1 - ratio) * (ckpt2[key].float()) + ).half() + + opt["config"] = cfg + opt["sr"] = message + opt["f0"] = cfg_f0 + opt["version"] = cfg_version + opt["info"] = message + + torch.save(opt, os.path.join("logs", "%s.pth" % name)) + print(message) + return message, os.path.join("logs", "%s.pth" % name) + except Exception as error: + print(error) + return error diff --git a/system/tts_engines/rvc/train/process/model_information.py b/system/tts_engines/rvc/train/process/model_information.py new file mode 100644 index 0000000..be74269 --- /dev/null +++ b/system/tts_engines/rvc/train/process/model_information.py @@ -0,0 +1,40 @@ +import torch +from datetime import datetime + + +def prettify_date(date_str): + if date_str is None: + return "None" + try: + date_time_obj = datetime.strptime(date_str, "%Y-%m-%dT%H:%M:%S.%f") + return date_time_obj.strftime("%Y-%m-%d %H:%M:%S") + except ValueError: + return "Invalid date format" + + +def model_information(path): + model_data = torch.load(path, map_location="cpu") + + print(f"Loaded model from {path}") + + epochs = model_data.get("epoch", "None") + steps = model_data.get("step", "None") + sr = model_data.get("sr", "None") + f0 = model_data.get("f0", "None") + version = model_data.get("version", "None") + creation_date = model_data.get("creation_date", "None") + model_hash = model_data.get("model_hash", None) + + pitch_guidance = "True" if f0 == 1 else "False" + + creation_date_str = prettify_date(creation_date) if creation_date else "None" + + return ( + f"Epochs: {epochs}\n" + f"Steps: {steps}\n" + f"RVC Version: {version}\n" + f"Sampling Rate: {sr}\n" + f"Pitch Guidance: {pitch_guidance}\n" + f"Creation Date: {creation_date_str}\n" + f"Hash (ID): {model_hash}" + ) diff --git a/system/tts_engines/rvc/train/slicer.py b/system/tts_engines/rvc/train/slicer.py new file mode 100644 index 0000000..c7e72bf --- /dev/null +++ b/system/tts_engines/rvc/train/slicer.py @@ -0,0 +1,171 @@ +import numpy as np + + +class Slicer: + def __init__( + self, + sr: int, + threshold: float = -40.0, + min_length: int = 5000, + min_interval: int = 300, + hop_size: int = 20, + max_sil_kept: int = 5000, + ): + if not min_length >= min_interval >= hop_size: + raise ValueError("min_length >= min_interval >= hop_size is required") + if not max_sil_kept >= hop_size: + raise ValueError("max_sil_kept >= hop_size is required") + + min_interval = sr * min_interval / 1000 + self.threshold = 10 ** (threshold / 20.0) + self.hop_size = round(sr * hop_size / 1000) + self.win_size = min(round(min_interval), 4 * self.hop_size) + self.min_length = round(sr * min_length / 1000 / self.hop_size) + self.min_interval = round(min_interval / self.hop_size) + self.max_sil_kept = round(sr * max_sil_kept / 1000 / self.hop_size) + + def _apply_slice(self, waveform, begin, end): + start_idx = begin * self.hop_size + if len(waveform.shape) > 1: + end_idx = min(waveform.shape[1], end * self.hop_size) + return waveform[:, start_idx:end_idx] + else: + end_idx = min(waveform.shape[0], end * self.hop_size) + return waveform[start_idx:end_idx] + + def slice(self, waveform): + samples = waveform.mean(axis=0) if len(waveform.shape) > 1 else waveform + if samples.shape[0] <= self.min_length: + return [waveform] + + rms_list = get_rms( + y=samples, frame_length=self.win_size, hop_length=self.hop_size + ).squeeze(0) + sil_tags = [] + silence_start, clip_start = None, 0 + + for i, rms in enumerate(rms_list): + if rms < self.threshold: + if silence_start is None: + silence_start = i + continue + + if silence_start is None: + continue + + is_leading_silence = silence_start == 0 and i > self.max_sil_kept + need_slice_middle = ( + i - silence_start >= self.min_interval + and i - clip_start >= self.min_length + ) + + if not is_leading_silence and not need_slice_middle: + silence_start = None + continue + + if i - silence_start <= self.max_sil_kept: + pos = rms_list[silence_start : i + 1].argmin() + silence_start + if silence_start == 0: + sil_tags.append((0, pos)) + else: + sil_tags.append((pos, pos)) + clip_start = pos + elif i - silence_start <= self.max_sil_kept * 2: + pos = rms_list[ + i - self.max_sil_kept : silence_start + self.max_sil_kept + 1 + ].argmin() + pos += i - self.max_sil_kept + pos_l = ( + rms_list[ + silence_start : silence_start + self.max_sil_kept + 1 + ].argmin() + + silence_start + ) + pos_r = ( + rms_list[i - self.max_sil_kept : i + 1].argmin() + + i + - self.max_sil_kept + ) + if silence_start == 0: + sil_tags.append((0, pos_r)) + clip_start = pos_r + else: + sil_tags.append((min(pos_l, pos), max(pos_r, pos))) + clip_start = max(pos_r, pos) + else: + pos_l = ( + rms_list[ + silence_start : silence_start + self.max_sil_kept + 1 + ].argmin() + + silence_start + ) + pos_r = ( + rms_list[i - self.max_sil_kept : i + 1].argmin() + + i + - self.max_sil_kept + ) + if silence_start == 0: + sil_tags.append((0, pos_r)) + else: + sil_tags.append((pos_l, pos_r)) + clip_start = pos_r + silence_start = None + + total_frames = rms_list.shape[0] + + if ( + silence_start is not None + and total_frames - silence_start >= self.min_interval + ): + silence_end = min(total_frames, silence_start + self.max_sil_kept) + pos = rms_list[silence_start : silence_end + 1].argmin() + silence_start + sil_tags.append((pos, total_frames + 1)) + + if not sil_tags: + return [waveform] + else: + chunks = [] + if sil_tags[0][0] > 0: + chunks.append(self._apply_slice(waveform, 0, sil_tags[0][0])) + + for i in range(len(sil_tags) - 1): + chunks.append( + self._apply_slice(waveform, sil_tags[i][1], sil_tags[i + 1][0]) + ) + + if sil_tags[-1][1] < total_frames: + chunks.append( + self._apply_slice(waveform, sil_tags[-1][1], total_frames) + ) + + return chunks + + +def get_rms( + y, + frame_length=2048, + hop_length=512, + pad_mode="constant", +): + padding = (int(frame_length // 2), int(frame_length // 2)) + y = np.pad(y, padding, mode=pad_mode) + + axis = -1 + out_strides = y.strides + tuple([y.strides[axis]]) + x_shape_trimmed = list(y.shape) + x_shape_trimmed[axis] -= frame_length - 1 + out_shape = tuple(x_shape_trimmed) + tuple([frame_length]) + xw = np.lib.stride_tricks.as_strided(y, shape=out_shape, strides=out_strides) + + if axis < 0: + target_axis = axis - 1 + else: + target_axis = axis + 1 + + xw = np.moveaxis(xw, -1, target_axis) + slices = [slice(None)] * xw.ndim + slices[axis] = slice(0, None, hop_length) + x = xw[tuple(slices)] + + power = np.mean(np.abs(x) ** 2, axis=-2, keepdims=True) + return np.sqrt(power) diff --git a/system/tts_engines/rvc/train/train.py b/system/tts_engines/rvc/train/train.py new file mode 100644 index 0000000..d67b3e4 --- /dev/null +++ b/system/tts_engines/rvc/train/train.py @@ -0,0 +1,720 @@ +import torch +import sys +import os +import datetime +import json +import re + +from utils import ( + get_hparams, + plot_spectrogram_to_numpy, + summarize, + load_checkpoint, + save_checkpoint, + latest_checkpoint_path, +) +from random import randint, shuffle +from time import sleep +from time import time as ttime + +from torch.cuda.amp import GradScaler, autocast + +from torch.nn import functional as F +from torch.nn.parallel import DistributedDataParallel as DDP +from torch.utils.data import DataLoader +from torch.utils.tensorboard import SummaryWriter +import torch.distributed as dist +import torch.multiprocessing as mp + +now_dir = os.getcwd() +sys.path.append(os.path.join(now_dir)) + + +from data_utils import ( + DistributedBucketSampler, + TextAudioCollate, + TextAudioCollateMultiNSFsid, + TextAudioLoader, + TextAudioLoaderMultiNSFsid, +) + +from losses import ( + discriminator_loss, + feature_loss, + generator_loss, + kl_loss, +) +from mel_processing import mel_spectrogram_torch, spec_to_mel_torch + +from rvc.train.process.extract_model import extract_model + +from rvc.lib.infer_pack import commons + +hps = get_hparams() +if hps.version == "v1": + from rvc.lib.infer_pack.models import MultiPeriodDiscriminator + from rvc.lib.infer_pack.models import SynthesizerTrnMs256NSFsid as RVC_Model_f0 + from rvc.lib.infer_pack.models import ( + SynthesizerTrnMs256NSFsid_nono as RVC_Model_nof0, + ) +elif hps.version == "v2": + from rvc.lib.infer_pack.models import ( + SynthesizerTrnMs768NSFsid as RVC_Model_f0, + SynthesizerTrnMs768NSFsid_nono as RVC_Model_nof0, + MultiPeriodDiscriminatorV2 as MultiPeriodDiscriminator, + ) + +os.environ["CUDA_VISIBLE_DEVICES"] = hps.gpus.replace("-", ",") +n_gpus = len(hps.gpus.split("-")) + + +torch.backends.cudnn.deterministic = False +torch.backends.cudnn.benchmark = False + +global_step = 0 +lowest_value = {"step": 0, "value": float("inf"), "epoch": 0} +last_loss_gen_all = 0 +epochs_since_last_lowest = 0 + + +class EpochRecorder: + def __init__(self): + self.last_time = ttime() + + def record(self): + now_time = ttime() + elapsed_time = now_time - self.last_time + self.last_time = now_time + elapsed_time = round(elapsed_time, 1) + elapsed_time_str = str(datetime.timedelta(seconds=int(elapsed_time))) + current_time = datetime.datetime.now().strftime("%H:%M:%S") + return f"time={current_time} | training_speed={elapsed_time_str}" + + +def main(): + def start(): + children = [] + pid_file_path = os.path.join(now_dir, "rvc", "train", "train_pid.txt") + with open(pid_file_path, "w") as pid_file: + for i in range(n_gpus): + subproc = mp.Process( + target=run, + args=(i, n_gpus, hps), + ) + children.append(subproc) + subproc.start() + pid_file.write(str(subproc.pid) + "\n") + + for i in range(n_gpus): + children[i].join() + + n_gpus = torch.cuda.device_count() + + if torch.cuda.is_available() == False and torch.backends.mps.is_available() == True: + n_gpus = 1 + if n_gpus < 1: + print("GPU not detected, reverting to CPU (not recommended)") + n_gpus = 1 + + if hps.sync_graph == 1: + print( + "Sync graph is now activated! With sync graph enabled, the model undergoes a single epoch of training. Once the graphs are synchronized, training proceeds for the previously specified number of epochs." + ) + hps.custom_total_epoch = 1 + hps.custom_save_every_weights = "1" + start() + + logs_path = os.path.join(now_dir, "logs") + model_config_file = os.path.join(now_dir, "logs", hps.name, "config.json") + rvc_config_file = os.path.join( + now_dir, "rvc", "configs", hps.version, str(hps.sample_rate) + ".json" + ) + if not os.path.exists(rvc_config_file): + rvc_config_file = os.path.join( + now_dir, "rvc", "configs", "v1", str(hps.sample_rate) + ".json" + ) + + pattern = rf"{os.path.basename(hps.name)}_1e_(\d+)s\.pth" + + for filename in os.listdir(logs_path): + match = re.match(pattern, filename) + if match: + steps = int(match.group(1)) + + def edit_config(config_file): + with open(config_file, "r", encoding="utf8") as json_file: + config_data = json.load(json_file) + + config_data["train"]["log_interval"] = steps + + with open(config_file, "w", encoding="utf8") as json_file: + json.dump( + config_data, + json_file, + indent=2, + separators=(",", ": "), + ensure_ascii=False, + ) + + edit_config(model_config_file) + edit_config(rvc_config_file) + + for root, dirs, files in os.walk( + os.path.join(now_dir, "logs", hps.name), topdown=False + ): + for name in files: + file_path = os.path.join(root, name) + file_name, file_extension = os.path.splitext(name) + if file_extension == ".0": + os.remove(file_path) + elif ("D" in name or "G" in name) and file_extension == ".pth": + os.remove(file_path) + elif ( + "added" in name or "trained" in name + ) and file_extension == ".index": + os.remove(file_path) + for name in dirs: + if name == "eval": + folder_path = os.path.join(root, name) + for item in os.listdir(folder_path): + item_path = os.path.join(folder_path, item) + if os.path.isfile(item_path): + os.remove(item_path) + os.rmdir(folder_path) + + print("Successfully synchronized graphs!") + hps.custom_total_epoch = hps.total_epoch + hps.custom_save_every_weights = hps.save_every_weights + start() + else: + hps.custom_total_epoch = hps.total_epoch + hps.custom_save_every_weights = hps.save_every_weights + start() + + +def run( + rank, + n_gpus, + hps, +): + global global_step + if rank == 0: + writer = SummaryWriter(log_dir=hps.model_dir) + writer_eval = SummaryWriter(log_dir=os.path.join(hps.model_dir, "eval")) + + os.environ["MASTER_ADDR"] = "localhost" + os.environ["MASTER_PORT"] = str(randint(20000, 55555)) + dist.init_process_group( + backend="gloo", init_method="env://", world_size=n_gpus, rank=rank + ) + torch.manual_seed(hps.train.seed) + if torch.cuda.is_available(): + torch.cuda.set_device(rank) + + if hps.if_f0 == 1: + train_dataset = TextAudioLoaderMultiNSFsid(hps.data) + else: + train_dataset = TextAudioLoader(hps.data) + + train_sampler = DistributedBucketSampler( + train_dataset, + hps.train.batch_size * n_gpus, + [100, 200, 300, 400, 500, 600, 700, 800, 900], + num_replicas=n_gpus, + rank=rank, + shuffle=True, + ) + + if hps.if_f0 == 1: + collate_fn = TextAudioCollateMultiNSFsid() + else: + collate_fn = TextAudioCollate() + train_loader = DataLoader( + train_dataset, + num_workers=4, + shuffle=False, + pin_memory=True, + collate_fn=collate_fn, + batch_sampler=train_sampler, + persistent_workers=True, + prefetch_factor=8, + ) + if hps.if_f0 == 1: + net_g = RVC_Model_f0( + hps.data.filter_length // 2 + 1, + hps.train.segment_size // hps.data.hop_length, + **hps.model, + is_half=hps.train.fp16_run, + sr=hps.sample_rate, + ) + else: + net_g = RVC_Model_nof0( + hps.data.filter_length // 2 + 1, + hps.train.segment_size // hps.data.hop_length, + **hps.model, + is_half=hps.train.fp16_run, + ) + if torch.cuda.is_available(): + net_g = net_g.cuda(rank) + net_d = MultiPeriodDiscriminator(hps.model.use_spectral_norm) + if torch.cuda.is_available(): + net_d = net_d.cuda(rank) + optim_g = torch.optim.AdamW( + net_g.parameters(), + hps.train.learning_rate, + betas=hps.train.betas, + eps=hps.train.eps, + ) + optim_d = torch.optim.AdamW( + net_d.parameters(), + hps.train.learning_rate, + betas=hps.train.betas, + eps=hps.train.eps, + ) + if torch.cuda.is_available(): + net_g = DDP(net_g, device_ids=[rank]) + net_d = DDP(net_d, device_ids=[rank]) + else: + net_g = DDP(net_g) + net_d = DDP(net_d) + + try: + print("Starting training...") + _, _, _, epoch_str = load_checkpoint( + latest_checkpoint_path(hps.model_dir, "D_*.pth"), net_d, optim_d + ) + _, _, _, epoch_str = load_checkpoint( + latest_checkpoint_path(hps.model_dir, "G_*.pth"), net_g, optim_g + ) + global_step = (epoch_str - 1) * len(train_loader) + + except: + epoch_str = 1 + global_step = 0 + if hps.pretrainG != "": + if rank == 0: + print(f"Loaded pretrained_G {hps.pretrainG}") + if hasattr(net_g, "module"): + print( + net_g.module.load_state_dict( + torch.load(hps.pretrainG, map_location="cpu")["model"] + ) + ) + else: + print( + net_g.load_state_dict( + torch.load(hps.pretrainG, map_location="cpu")["model"] + ) + ) + if hps.pretrainD != "": + if rank == 0: + print(f"Loaded pretrained_D {hps.pretrainD}") + if hasattr(net_d, "module"): + print( + net_d.module.load_state_dict( + torch.load(hps.pretrainD, map_location="cpu")["model"] + ) + ) + else: + print( + net_d.load_state_dict( + torch.load(hps.pretrainD, map_location="cpu")["model"] + ) + ) + + scheduler_g = torch.optim.lr_scheduler.ExponentialLR( + optim_g, gamma=hps.train.lr_decay, last_epoch=epoch_str - 2 + ) + scheduler_d = torch.optim.lr_scheduler.ExponentialLR( + optim_d, gamma=hps.train.lr_decay, last_epoch=epoch_str - 2 + ) + + scaler = GradScaler(enabled=hps.train.fp16_run) + + cache = [] + for epoch in range(epoch_str, hps.train.epochs + 1): + if rank == 0: + train_and_evaluate( + rank, + epoch, + hps, + [net_g, net_d], + [optim_g, optim_d], + scaler, + [train_loader, None], + [writer, writer_eval], + cache, + ) + else: + train_and_evaluate( + rank, + epoch, + hps, + [net_g, net_d], + [optim_g, optim_d], + scaler, + [train_loader, None], + None, + cache, + ) + + scheduler_g.step() + scheduler_d.step() + + +def train_and_evaluate(rank, epoch, hps, nets, optims, scaler, loaders, writers, cache): + global global_step, last_loss_gen_all, lowest_value, epochs_since_last_lowest + + if epoch == 1: + lowest_value = {"step": 0, "value": float("inf"), "epoch": 0} + last_loss_gen_all = 0.0 + epochs_since_last_lowest = 0 + + net_g, net_d = nets + optim_g, optim_d = optims + train_loader = loaders[0] if loaders is not None else None + if writers is not None: + writer = writers[0] + + train_loader.batch_sampler.set_epoch(epoch) + + net_g.train() + net_d.train() + + if hps.if_cache_data_in_gpu == True: + data_iterator = cache + if cache == []: + for batch_idx, info in enumerate(train_loader): + if hps.if_f0 == 1: + ( + phone, + phone_lengths, + pitch, + pitchf, + spec, + spec_lengths, + wave, + wave_lengths, + sid, + ) = info + else: + ( + phone, + phone_lengths, + spec, + spec_lengths, + wave, + wave_lengths, + sid, + ) = info + if torch.cuda.is_available(): + phone = phone.cuda(rank, non_blocking=True) + phone_lengths = phone_lengths.cuda(rank, non_blocking=True) + if hps.if_f0 == 1: + pitch = pitch.cuda(rank, non_blocking=True) + pitchf = pitchf.cuda(rank, non_blocking=True) + sid = sid.cuda(rank, non_blocking=True) + spec = spec.cuda(rank, non_blocking=True) + spec_lengths = spec_lengths.cuda(rank, non_blocking=True) + wave = wave.cuda(rank, non_blocking=True) + wave_lengths = wave_lengths.cuda(rank, non_blocking=True) + if hps.if_f0 == 1: + cache.append( + ( + batch_idx, + ( + phone, + phone_lengths, + pitch, + pitchf, + spec, + spec_lengths, + wave, + wave_lengths, + sid, + ), + ) + ) + else: + cache.append( + ( + batch_idx, + ( + phone, + phone_lengths, + spec, + spec_lengths, + wave, + wave_lengths, + sid, + ), + ) + ) + else: + shuffle(cache) + else: + data_iterator = enumerate(train_loader) + + epoch_recorder = EpochRecorder() + for batch_idx, info in data_iterator: + if hps.if_f0 == 1: + ( + phone, + phone_lengths, + pitch, + pitchf, + spec, + spec_lengths, + wave, + wave_lengths, + sid, + ) = info + else: + phone, phone_lengths, spec, spec_lengths, wave, wave_lengths, sid = info + if (hps.if_cache_data_in_gpu == False) and torch.cuda.is_available(): + phone = phone.cuda(rank, non_blocking=True) + phone_lengths = phone_lengths.cuda(rank, non_blocking=True) + if hps.if_f0 == 1: + pitch = pitch.cuda(rank, non_blocking=True) + pitchf = pitchf.cuda(rank, non_blocking=True) + sid = sid.cuda(rank, non_blocking=True) + spec = spec.cuda(rank, non_blocking=True) + spec_lengths = spec_lengths.cuda(rank, non_blocking=True) + wave = wave.cuda(rank, non_blocking=True) + + with autocast(enabled=hps.train.fp16_run): + if hps.if_f0 == 1: + ( + y_hat, + ids_slice, + x_mask, + z_mask, + (z, z_p, m_p, logs_p, m_q, logs_q), + ) = net_g(phone, phone_lengths, pitch, pitchf, spec, spec_lengths, sid) + else: + ( + y_hat, + ids_slice, + x_mask, + z_mask, + (z, z_p, m_p, logs_p, m_q, logs_q), + ) = net_g(phone, phone_lengths, spec, spec_lengths, sid) + mel = spec_to_mel_torch( + spec, + hps.data.filter_length, + hps.data.n_mel_channels, + hps.data.sampling_rate, + hps.data.mel_fmin, + hps.data.mel_fmax, + ) + y_mel = commons.slice_segments( + mel, ids_slice, hps.train.segment_size // hps.data.hop_length + ) + with autocast(enabled=False): + y_hat_mel = mel_spectrogram_torch( + y_hat.float().squeeze(1), + hps.data.filter_length, + hps.data.n_mel_channels, + hps.data.sampling_rate, + hps.data.hop_length, + hps.data.win_length, + hps.data.mel_fmin, + hps.data.mel_fmax, + ) + if hps.train.fp16_run == True: + y_hat_mel = y_hat_mel.half() + wave = commons.slice_segments( + wave, ids_slice * hps.data.hop_length, hps.train.segment_size + ) + + y_d_hat_r, y_d_hat_g, _, _ = net_d(wave, y_hat.detach()) + with autocast(enabled=False): + loss_disc, losses_disc_r, losses_disc_g = discriminator_loss( + y_d_hat_r, y_d_hat_g + ) + optim_d.zero_grad() + scaler.scale(loss_disc).backward() + scaler.unscale_(optim_d) + grad_norm_d = commons.clip_grad_value_(net_d.parameters(), None) + scaler.step(optim_d) + + with autocast(enabled=hps.train.fp16_run): + y_d_hat_r, y_d_hat_g, fmap_r, fmap_g = net_d(wave, y_hat) + with autocast(enabled=False): + loss_mel = F.l1_loss(y_mel, y_hat_mel) * hps.train.c_mel + loss_kl = kl_loss(z_p, logs_q, m_p, logs_p, z_mask) * hps.train.c_kl + loss_fm = feature_loss(fmap_r, fmap_g) + loss_gen, losses_gen = generator_loss(y_d_hat_g) + loss_gen_all = loss_gen + loss_fm + loss_mel + loss_kl + + if loss_gen_all < lowest_value["value"]: + lowest_value["value"] = loss_gen_all + lowest_value["step"] = global_step + lowest_value["epoch"] = epoch + # print(f'Lowest generator loss updated: {lowest_value["value"]} at epoch {epoch}, step {global_step}') + if epoch > lowest_value["epoch"]: + print( + "Alert: The lower generating loss has been exceeded by a lower loss in a subsequent epoch." + ) + + optim_g.zero_grad() + scaler.scale(loss_gen_all).backward() + scaler.unscale_(optim_g) + grad_norm_g = commons.clip_grad_value_(net_g.parameters(), None) + scaler.step(optim_g) + scaler.update() + + if rank == 0: + if global_step % hps.train.log_interval == 0: + lr = optim_g.param_groups[0]["lr"] + # print("Epoch: {} [{:.0f}%]".format(epoch, 100.0 * batch_idx / len(train_loader))) + + if loss_mel > 75: + loss_mel = 75 + if loss_kl > 9: + loss_kl = 9 + + scalar_dict = { + "loss/g/total": loss_gen_all, + "loss/d/total": loss_disc, + "learning_rate": lr, + "grad_norm_d": grad_norm_d, + "grad_norm_g": grad_norm_g, + } + scalar_dict.update( + { + "loss/g/fm": loss_fm, + "loss/g/mel": loss_mel, + "loss/g/kl": loss_kl, + } + ) + + scalar_dict.update( + {"loss/g/{}".format(i): v for i, v in enumerate(losses_gen)} + ) + scalar_dict.update( + {"loss/d_r/{}".format(i): v for i, v in enumerate(losses_disc_r)} + ) + scalar_dict.update( + {"loss/d_g/{}".format(i): v for i, v in enumerate(losses_disc_g)} + ) + image_dict = { + "slice/mel_org": plot_spectrogram_to_numpy( + y_mel[0].data.cpu().numpy() + ), + "slice/mel_gen": plot_spectrogram_to_numpy( + y_hat_mel[0].data.cpu().numpy() + ), + "all/mel": plot_spectrogram_to_numpy(mel[0].data.cpu().numpy()), + } + summarize( + writer=writer, + global_step=global_step, + images=image_dict, + scalars=scalar_dict, + ) + + # optim_g.step() + # optim_d.step() + + global_step += 1 + + if epoch % hps.save_every_epoch == 0 and rank == 0: + checkpoint_suffix = "{}.pth".format( + global_step if hps.if_latest == 0 else 2333333 + ) + save_checkpoint( + net_g, + optim_g, + hps.train.learning_rate, + epoch, + os.path.join(hps.model_dir, "G_" + checkpoint_suffix), + ) + save_checkpoint( + net_d, + optim_d, + hps.train.learning_rate, + epoch, + os.path.join(hps.model_dir, "D_" + checkpoint_suffix), + ) + + if rank == 0 and hps.custom_save_every_weights == "1": + if hasattr(net_g, "module"): + ckpt = net_g.module.state_dict() + else: + ckpt = net_g.state_dict() + extract_model( + ckpt, + hps.sample_rate, + hps.if_f0, + hps.name, + os.path.join( + hps.model_dir, "{}_{}e_{}s.pth".format(hps.name, epoch, global_step) + ), + epoch, + global_step, + hps.version, + hps, + ) + + if hps.overtraining_detector == 1: + if lowest_value["value"] < last_loss_gen_all: + epochs_since_last_lowest += 1 + else: + epochs_since_last_lowest = 0 + + if epochs_since_last_lowest >= hps.overtraining_threshold: + print( + "Stopping training due to possible overtraining. Lowest generator loss: {} at epoch {}, step {}".format( + lowest_value["value"], lowest_value["epoch"], lowest_value["step"] + ) + ) + os._exit(2333333) + + if rank == 0: + if epoch > 1: + print( + f"{hps.name} | epoch={epoch} | step={global_step} | {epoch_recorder.record()} | lowest_value={lowest_value['value']} (epoch {lowest_value['epoch']} and step {lowest_value['step']})" + ) + else: + print( + f"{hps.name} | epoch={epoch} | step={global_step} | {epoch_recorder.record()}" + ) + last_loss_gen_all = loss_gen_all + + if epoch >= hps.custom_total_epoch and rank == 0: + print( + f"Training has been successfully completed with {epoch} epoch, {global_step} steps and {round(loss_gen_all.item(), 3)} loss gen." + ) + print( + f"Lowest generator loss: {lowest_value['value']} at epoch {lowest_value['epoch']}, step {lowest_value['step']}" + ) + + pid_file_path = os.path.join(now_dir, "rvc", "train", "train_pid.txt") + os.remove(pid_file_path) + + if hasattr(net_g, "module"): + ckpt = net_g.module.state_dict() + else: + ckpt = net_g.state_dict() + + extract_model( + ckpt, + hps.sample_rate, + hps.if_f0, + hps.name, + os.path.join( + hps.model_dir, "{}_{}e_{}s.pth".format(hps.name, epoch, global_step) + ), + epoch, + global_step, + hps.version, + hps, + ) + sleep(1) + os._exit(2333333) + + +if __name__ == "__main__": + torch.multiprocessing.set_start_method("spawn") + main() diff --git a/system/tts_engines/rvc/train/utils.py b/system/tts_engines/rvc/train/utils.py new file mode 100644 index 0000000..7584b64 --- /dev/null +++ b/system/tts_engines/rvc/train/utils.py @@ -0,0 +1,311 @@ +import os +import glob +import json +import torch +import argparse +import numpy as np +from scipy.io.wavfile import read +from collections import OrderedDict + + +def replace_keys_in_dict(d, old_key_part, new_key_part): + if isinstance(d, OrderedDict): + updated_dict = OrderedDict() + else: + updated_dict = {} + for key, value in d.items(): + if isinstance(key, str): + new_key = key.replace(old_key_part, new_key_part) + else: + new_key = key + if isinstance(value, dict): + value = replace_keys_in_dict(value, old_key_part, new_key_part) + updated_dict[new_key] = value + return updated_dict + + +def load_checkpoint(checkpoint_path, model, optimizer=None, load_opt=1): + assert os.path.isfile(checkpoint_path) + checkpoint_old_dict = torch.load(checkpoint_path, map_location="cpu") + checkpoint_new_version_path = os.path.join( + os.path.dirname(checkpoint_path), + f"{os.path.splitext(os.path.basename(checkpoint_path))[0]}_new_version.pth", + ) + + torch.save( + replace_keys_in_dict( + replace_keys_in_dict( + checkpoint_old_dict, ".weight_v", ".parametrizations.weight.original1" + ), + ".weight_g", + ".parametrizations.weight.original0", + ), + checkpoint_new_version_path, + ) + + os.remove(checkpoint_path) + os.rename(checkpoint_new_version_path, checkpoint_path) + + checkpoint_dict = torch.load(checkpoint_path, map_location="cpu") + saved_state_dict = checkpoint_dict["model"] + if hasattr(model, "module"): + state_dict = model.module.state_dict() + else: + state_dict = model.state_dict() + new_state_dict = {} + for k, v in state_dict.items(): + try: + new_state_dict[k] = saved_state_dict[k] + if saved_state_dict[k].shape != state_dict[k].shape: + print( + "shape-%s-mismatch|need-%s|get-%s", + k, + state_dict[k].shape, + saved_state_dict[k].shape, + ) + raise KeyError + except: + print("%s is not in the checkpoint", k) + new_state_dict[k] = v + if hasattr(model, "module"): + model.module.load_state_dict(new_state_dict, strict=False) + else: + model.load_state_dict(new_state_dict, strict=False) + + iteration = checkpoint_dict["iteration"] + learning_rate = checkpoint_dict["learning_rate"] + if optimizer is not None and load_opt == 1: + optimizer.load_state_dict(checkpoint_dict["optimizer"]) + print(f"Loaded checkpoint '{checkpoint_path}' (epoch {iteration})") + return model, optimizer, learning_rate, iteration + + +def save_checkpoint(model, optimizer, learning_rate, iteration, checkpoint_path): + print(f"Saved model '{checkpoint_path}' (epoch {iteration})") + checkpoint_old_version_path = os.path.join( + os.path.dirname(checkpoint_path), + f"{os.path.splitext(os.path.basename(checkpoint_path))[0]}_old_version.pth", + ) + if hasattr(model, "module"): + state_dict = model.module.state_dict() + else: + state_dict = model.state_dict() + torch.save( + { + "model": state_dict, + "iteration": iteration, + "optimizer": optimizer.state_dict(), + "learning_rate": learning_rate, + }, + checkpoint_path, + ) + checkpoint = torch.load(checkpoint_path, map_location=torch.device("cpu")) + torch.save( + replace_keys_in_dict( + replace_keys_in_dict( + checkpoint, ".parametrizations.weight.original1", ".weight_v" + ), + ".parametrizations.weight.original0", + ".weight_g", + ), + checkpoint_old_version_path, + ) + os.remove(checkpoint_path) + os.rename(checkpoint_old_version_path, checkpoint_path) + + +def summarize( + writer, + global_step, + scalars={}, + histograms={}, + images={}, + audios={}, + audio_sampling_rate=22050, +): + for k, v in scalars.items(): + writer.add_scalar(k, v, global_step) + for k, v in histograms.items(): + writer.add_histogram(k, v, global_step) + for k, v in images.items(): + writer.add_image(k, v, global_step, dataformats="HWC") + for k, v in audios.items(): + writer.add_audio(k, v, global_step, audio_sampling_rate) + + +def latest_checkpoint_path(dir_path, regex="G_*.pth"): + f_list = glob.glob(os.path.join(dir_path, regex)) + f_list.sort(key=lambda f: int("".join(filter(str.isdigit, f)))) + x = f_list[-1] + return x + + +def plot_spectrogram_to_numpy(spectrogram): + import matplotlib.pylab as plt + import numpy as np + + fig, ax = plt.subplots(figsize=(10, 2)) + im = ax.imshow(spectrogram, aspect="auto", origin="lower", interpolation="none") + plt.colorbar(im, ax=ax) + plt.xlabel("Frames") + plt.ylabel("Channels") + plt.tight_layout() + + fig.canvas.draw() + data = np.fromstring(fig.canvas.tostring_rgb(), dtype=np.uint8, sep="") + data = data.reshape(fig.canvas.get_width_height()[::-1] + (3,)) + plt.close() + return data + + +def load_wav_to_torch(full_path): + sampling_rate, data = read(full_path) + return torch.FloatTensor(data.astype(np.float32)), sampling_rate + + +def load_filepaths_and_text(filename, split="|"): + with open(filename, encoding="utf-8") as f: + filepaths_and_text = [line.strip().split(split) for line in f] + return filepaths_and_text + + +def get_hparams(): + parser = argparse.ArgumentParser() + parser.add_argument( + "-se", + "--save_every_epoch", + type=int, + required=True, + help="checkpoint save frequency (epoch)", + ) + parser.add_argument( + "-te", "--total_epoch", type=int, required=True, help="total_epoch" + ) + parser.add_argument( + "-pg", "--pretrainG", type=str, default="", help="Pretrained Discriminator path" + ) + parser.add_argument( + "-pd", "--pretrainD", type=str, default="", help="Pretrained Generator path" + ) + parser.add_argument("-g", "--gpus", type=str, default="0", help="split by -") + parser.add_argument( + "-bs", "--batch_size", type=int, required=True, help="batch size" + ) + parser.add_argument( + "-e", "--experiment_dir", type=str, required=True, help="experiment dir" + ) + parser.add_argument( + "-sr", "--sample_rate", type=str, required=True, help="sample rate, 32k/40k/48k" + ) + parser.add_argument( + "-sw", + "--save_every_weights", + type=str, + default="0", + help="save the extracted model in weights directory when saving checkpoints", + ) + parser.add_argument( + "-v", "--version", type=str, required=True, help="model version" + ) + parser.add_argument( + "-f0", + "--if_f0", + type=int, + required=True, + help="use f0 as one of the inputs of the model, 1 or 0", + ) + parser.add_argument( + "-l", + "--if_latest", + type=int, + required=True, + help="if only save the latest G/D pth file, 1 or 0", + ) + parser.add_argument( + "-c", + "--if_cache_data_in_gpu", + type=int, + required=True, + help="if caching the dataset in GPU memory, 1 or 0", + ) + + parser.add_argument( + "-od", + "--overtraining_detector", + type=int, + required=True, + help="Detect overtraining or not, 1 or 0", + ) + parser.add_argument( + "-ot", + "--overtraining_threshold", + type=int, + default=50, + help="overtraining_threshold", + ) + parser.add_argument( + "-sg", + "--sync-graph", + type=int, + required=True, + help="Sync graph or not, 1 or 0", + ) + + args = parser.parse_args() + name = args.experiment_dir + experiment_dir = os.path.join("./logs", args.experiment_dir) + config_save_path = os.path.join(experiment_dir, "config.json") + with open(config_save_path, "r") as f: + config = json.load(f) + hparams = HParams(**config) + hparams.model_dir = hparams.experiment_dir = experiment_dir + hparams.save_every_epoch = args.save_every_epoch + hparams.name = name + hparams.total_epoch = args.total_epoch + hparams.pretrainG = args.pretrainG + hparams.pretrainD = args.pretrainD + hparams.version = args.version + hparams.gpus = args.gpus + hparams.train.batch_size = args.batch_size + hparams.sample_rate = args.sample_rate + hparams.if_f0 = args.if_f0 + hparams.if_latest = args.if_latest + hparams.save_every_weights = args.save_every_weights + hparams.if_cache_data_in_gpu = args.if_cache_data_in_gpu + hparams.data.training_files = f"{experiment_dir}/filelist.txt" + hparams.overtraining_detector = args.overtraining_detector + hparams.overtraining_threshold = args.overtraining_threshold + hparams.sync_graph = args.sync_graph + return hparams + + +class HParams: + def __init__(self, **kwargs): + for k, v in kwargs.items(): + if type(v) == dict: + v = HParams(**v) + self[k] = v + + def keys(self): + return self.__dict__.keys() + + def items(self): + return self.__dict__.items() + + def values(self): + return self.__dict__.values() + + def __len__(self): + return len(self.__dict__) + + def __getitem__(self, key): + return getattr(self, key) + + def __setitem__(self, key, value): + return setattr(self, key, value) + + def __contains__(self, key): + return key in self.__dict__ + + def __repr__(self): + return self.__dict__.__repr__() diff --git a/system/tts_engines/rvc_files.json b/system/tts_engines/rvc_files.json new file mode 100644 index 0000000..5da1884 --- /dev/null +++ b/system/tts_engines/rvc_files.json @@ -0,0 +1,6 @@ +{ + "fcpe.pt": "https://huggingface.co/erew123/atrvc/resolve/main/fcpe.pt?download=true", + "hubert_base.pt": "https://huggingface.co/erew123/atrvc/resolve/main/hubert_base.pt?download=true", + "rmvpe.onnx": "https://huggingface.co/erew123/atrvc/resolve/main/rmvpe.onnx?download=true", + "rmvpe.pt": "https://huggingface.co/erew123/atrvc/resolve/main/rmvpe.pt?download=true" +} diff --git a/system/tts_engines/template-tts-engine/TTS Engine Setup Instructions.docx b/system/tts_engines/template-tts-engine/TTS Engine Setup Instructions.docx new file mode 100644 index 0000000..63c6500 Binary files /dev/null and b/system/tts_engines/template-tts-engine/TTS Engine Setup Instructions.docx differ diff --git a/system/tts_engines/template-tts-engine/available_models.json b/system/tts_engines/template-tts-engine/available_models.json new file mode 100644 index 0000000..42c8c96 --- /dev/null +++ b/system/tts_engines/template-tts-engine/available_models.json @@ -0,0 +1,40 @@ +{ + "first_start_model": "en_GB-jenny_dioco-medium", + "models": [ + { + "model_name": "ar_JO-kareem-low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/ar/ar_JO/kareem/low/ar_JO-kareem-low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/ar/ar_JO/kareem/low/ar_JO-kareem-low.onnx.json?download=true" + ] + }, + { + "model_name": "ar_JO-kareem-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/ar/ar_JO/kareem/medium/ar_JO-kareem-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/ar/ar_JO/kareem/medium/ar_JO-kareem-medium.onnx.json?download=true" + ] + }, + { + "model_name": "vi_VN-vivos-x_low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/vi/vi_VN/vivos/x_low/vi_VN-vivos-x_low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/vi/vi_VN/vivos/x_low/vi_VN-vivos-x_low.onnx.json?download=true" + ] + }, + { + "model_name": "zh_CN-huayan-medium", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/zh/zh_CN/huayan/medium/zh_CN-huayan-medium.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/zh/zh_CN/huayan/medium/zh_CN-huayan-medium.onnx.json?download=true" + ] + }, + { + "model_name": "zh_CN-huayan-x_low", + "files_to_download": [ + "https://huggingface.co/rhasspy/piper-voices/resolve/main/zh/zh_CN/huayan/x_low/zh_CN-huayan-x_low.onnx?download=true", + "https://huggingface.co/rhasspy/piper-voices/resolve/main/zh/zh_CN/huayan/x_low/zh_CN-huayan-x_low.onnx.json?download=true" + ] + } + ] +} \ No newline at end of file diff --git a/system/tts_engines/template-tts-engine/model_engine.py b/system/tts_engines/template-tts-engine/model_engine.py new file mode 100644 index 0000000..5f78bb6 --- /dev/null +++ b/system/tts_engines/template-tts-engine/model_engine.py @@ -0,0 +1,421 @@ +############################################### +# DONT CHANGE # These are base imports needed # +############################################### +import os +import sys +import json +import time +import torch +import logging +from pathlib import Path +from fastapi import (HTTPException) +logging.disable(logging.WARNING) +################################################################# +# DONT CHANGE # Get Pytorch & Python versions & setup DeepSpeed # +################################################################# +pytorch_version = torch.__version__ +cuda_version = torch.version.cuda +major, minor, micro = sys.version_info[:3] +python_version = f"{major}.{minor}.{micro}" +try: + import deepspeed + deepspeed_available = True +except ImportError: + deepspeed_available = False + pass + +############################################################################################################# +############################################################################################################# +# CHANGE ME # Run any specifc imports, requirements or setup any global vaiables needed for this TTS Engine # +############################################################################################################# +############################################################################################################# +# In this section you will import any imports that your specific TTS Engine will use. You will provide any +# start-up errors for those bits, as if you were starting up a normal Python script. Note the logging.disable +# a few lines up from here, you may want to # that out while debugging! + + + + + +################################################################################################################################# +# DONT CHANGE # Do not change the Class name from tts_class as this is what will be imported into the main tts_server.py script # +################################################################################################################################# +class tts_class: + def __init__(self): + ######################################################################## + # DONT CHANGE # Sets up the base variables required for any tts engine # + ######################################################################## + self.branding = None + self.this_dir = Path(__file__).parent.resolve() # Sets up self.this_dir as a variable for the folder THIS script is running in. + self.main_dir = Path(__file__).parent.parent.parent.parent.resolve() # Sets up self.main_dir as a variable for the folder AllTalk is running in + self.device = "cuda" if torch.cuda.is_available() else "cpu" # Sets up self.device to cuda if torch exists with Nvidia/CUDA, otherwise sets to cpu + self.cuda_is_available = torch.cuda.is_available() # Sets up cuda_is_available as a True/False to track if Nvidia/CUDA was found on the system + self.tts_generating_lock = False # Used to lock and unlock the tts generation process at the start/end of tts generation. + self.tts_stop_generation = False # Used in conjunction with tts_generating_lock to call for a stop to the current generation. If called (set True) it needs to be set back to False when generation has been stopped. + self.tts_narrator_generatingtts = False # Used to track if the current tts processes is narrator based. This can be used in conjunction with lowvram and device to avoid moving model between GPU(CUDA)<>RAM(CPU) each chunk of narrated text generated. + self.model = None # If loading a model into CUDA/VRAM/RAM "model" is used as the variable name to load and interact with (see the XTTS model_engine script for examples.) + self.is_tts_model_loaded = False # Used to track if a model is actually loaded in and error/fail things like TTS generation if its False + self.current_model_loaded = None # Stores the name of the currenly loaded in model + self.available_models = None # List of available models found by "def scan_models_folder" + self.setup_has_run = False # Tracks if async def setup(self) has run, by setting to True, so that the /api/ready endpoint can provide a "Ready" status + ############################################################################################## + # DONT CHANGE # Load in a list of the available TTS engines and the currently set TTS engine # + ############################################################################################## + tts_engines_file = os.path.join(self.main_dir, "system", "tts_engines", "tts_engines.json") + with open(tts_engines_file, "r") as f: + tts_engines_data = json.load(f) + self.engines_available = [engine["name"] for engine in tts_engines_data["engines_available"]] # A list of ALL the TTS engines available to be loaded by AllTalk + self.engine_loaded = tts_engines_data["engine_loaded"] # In "tts_engines.json" what is the currently set TTS engine loading into AllTalk + self.selected_model = tts_engines_data["selected_model"] # In "tts_engines.json" what is the currently set TTS model loading into AllTalk + ############################################################################ + # DONT CHANGE # Pull out all the settings for the currently set TTS engine # + ############################################################################ + with open(os.path.join(self.this_dir, "model_settings.json"), "r") as f: + tts_model_loaded = json.load(f) + # Access the model details + self.manufacturer_name = tts_model_loaded["model_details"]["manufacturer_name"] # The company/person/body that generated the TTS engine/models etc + self.manufacturer_website = tts_model_loaded["model_details"]["manufacturer_website"] # The website of the company/person/body where people can find more information + # Access the features the model is capable of: + self.audio_format = tts_model_loaded["model_capabilties"]["audio_format"] # This details the audio format your TTS engine is set to generate TTS in e.g. wav, mp3, flac, opus, acc, pcm. Please use only 1x format. + self.deepspeed_capable = tts_model_loaded["model_capabilties"]["deepspeed_capable"] # Is your model capable of DeepSpeed + self.deepspeed_available = 'deepspeed' in globals() # When we did the import earlier, at the top of this script, was DeepSpeed available for use + self.generationspeed_capable = tts_model_loaded["model_capabilties"]["generationspeed_capable"] # Does this TTS engine support changing the speed of the generated TTS + self.languages_capable = tts_model_loaded["model_capabilties"]["languages_capable"] # Are the actual models themselves capable of generating in multiple languages OR is each model language specific + self.lowvram_capable = tts_model_loaded["model_capabilties"]["lowvram_capable"] # Is this engine capable of using low VRAM (moving the model between CPU And GPU memory) + self.multimodel_capable = tts_model_loaded["model_capabilties"]["multimodel_capable"] # Is there just the one model or are there multiple models this engine supports. + self.repetitionpenalty_capable = tts_model_loaded["model_capabilties"]["repetitionpenalty_capable"] # Is this TTS engine capable of changing the repititon penalty + self.streaming_capable = tts_model_loaded["model_capabilties"]["streaming_capable"] # Is this TTS engine capabale of generating streaming audio + self.temperature_capable = tts_model_loaded["model_capabilties"]["temperature_capable"] # Is this TTS engine capable of changing the temperature of the models + self.multivoice_capable = tts_model_loaded["model_capabilties"]["multivoice_capable"] # Are the models multi-voice or single vocice models + self.pitch_capable = tts_model_loaded["model_capabilties"]["pitch_capable"] # Is this TTS engine capable of changing the pitch of the genrated TTS + # Access the current enginesettings + self.def_character_voice = tts_model_loaded["settings"]["def_character_voice"] # What is the current default main/character voice that will be used if no voice specified. + self.def_narrator_voice = tts_model_loaded["settings"]["def_narrator_voice"] # What is the current default narrator voice that will be used if no voice specified. + self.deepspeed_enabled = tts_model_loaded["settings"]["deepspeed_enabled"] # If its available, is DeepSpeed enabled for the TTS engine + self.engine_installed = tts_model_loaded["settings"]["engine_installed"] # Has the TTS engine been setup/installed (not curently used) + self.generationspeed_set = tts_model_loaded["settings"]["generationspeed_set"] # What is the set/stored speed for generation. + self.lowvram_enabled = tts_model_loaded["settings"]["lowvram_enabled"] # If its available, is LowVRAM enabled for the TTS engine + # Check if someone has enabled lowvram on a system that's not CUDA enabled + self.lowvram_enabled = False if not torch.cuda.is_available() else self.lowvram_enabled # If LowVRAM is mistakenly set and CUDA is not available, this will force it back off + self.repetitionpenalty_set = tts_model_loaded["settings"]["repetitionpenalty_set"] # What is the currenly set repitition policy of the model (If it support repetition) + self.temperature_set = tts_model_loaded["settings"]["temperature_set"] # What is the currenly set temperature of the model (If it support temp) + self.pitch_set = tts_model_loaded["settings"]["pitch_set"] # What is the currenly set pitch of the model (If it support temp) + # Gather the OpenAI API Voice Mappings + self.openai_alloy = tts_model_loaded["openai_voices"]["alloy"] # The TTS engine voice that will be mapped to Open AI Alloy voice + self.openai_echo = tts_model_loaded["openai_voices"]["echo"] # The TTS engine voice that will be mapped to Open AI Echo voice + self.openai_fable = tts_model_loaded["openai_voices"]["fable"] # The TTS engine voice that will be mapped to Open AI Fable voice + self.openai_nova = tts_model_loaded["openai_voices"]["nova"] # The TTS engine voice that will be mapped to Open AI Nova voice + self.openai_onyx = tts_model_loaded["openai_voices"]["onyx"] # The TTS engine voice that will be mapped to Open AI Onyx voice + self.openai_shimmer = tts_model_loaded["openai_voices"]["shimmer"] # The TTS engine voice that will be mapped to Open AI Shimmer voice + ################################################################### + # DONT CHANGE # Load params and api_defaults from confignew.json # + ################################################################### + # Define the path to the confignew.json file + configfile_path = self.main_dir / "confignew.json" + # Load config file and get settings + with open(configfile_path, "r") as configfile: + configfile_data = json.load(configfile) + self.branding = configfile_data.get("branding", "") # Sets up self.branding for outputting the name stored in the "confgnew.json" file, as used in print statements. + self.params = configfile_data # Loads in the curent "confgnew.json" file to self.params. + self.debug_tts = configfile_data.get("debugging").get("debug_tts") # Can be used within this script as a True/False flag for generally debugging the TTS generation process. + self.debug_tts_variables = configfile_data.get("debugging").get("debug_tts_variables") # Can be used within this script as a True/False flag for generally debugging variables (if you wish). + ################################################################ + # DONT CHANGE # Print out Python, CUDA, DeepSpeed versions #### + ################################################################ + def printout_versions(self): + if deepspeed_available: + print(f"[{self.branding}ENG] \033[92mDeepSpeed version :\033[93m",deepspeed.__version__,"\033[0m") + else: + print(f"[{self.branding}ENG] \033[92mDeepSpeed version :\033[93m Not available\033[0m") + print(f"[{self.branding}ENG] \033[92mPython Version :\033[93m {python_version}\033[0m") + print(f"[{self.branding}ENG] \033[92mPyTorch Version :\033[93m {pytorch_version}\033[0m") + if cuda_version is None: + print(f"[{self.branding}ENG] \033[92mCUDA Version :\033[91m Not available\033[0m") + else: + print(f"[{self.branding}ENG] \033[92mCUDA Version :\033[93m {cuda_version}\033[0m") + print(f"[{self.branding}ENG]") + return + + ################################################################################### + ################################################################################### + # CHANGE ME # Inital setup of the model and engine. Called when the script starts # + ################################################################################### + ################################################################################### + # In here you will add code to load in your model and do its inital setup. So you + # may be calling your model loader via handle_tts_method_change or if your TTS + # engine doesnt actually load a model into CUDA or System RAM, you may be doing + # Something to fake its start-up. + async def setup(self): + self.printout_versions() + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + + + + + + + + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + self.setup_has_run = True # Flag that setup has run, so the /api/ready endpoint will send a "Ready" status and load the webui + + ################################## + ################################## + # CHANGE ME # Low VRAM Swapping # + ################################## + ################################## + # If your model does load into CUDA and you want to support LowVRAM, aka moving the model + # on the fly between CUDA and System RAM on each generation request, you will be adding + # The code in here to do it. See the XTTS tts engine for an example. Piper however, doesnt + # Load into VRAM or System RAM, also low vram is set globally disabled by the model_settings.JSON + # file, so this would never get called anywyay, however, we still need to keep an empty + # function in place. The "pass" tells the function to just exit out cleanly if called. + # However, its quite a simple check along the lines of "if CUDA is available and model is + # in X place, then send it to Y place (or Y to X). + async def handle_lowvram_change(self): + pass # Piper does not stay in CUDA or support swapping of location + + ######################################## + ######################################## + # CHANGE ME # DeepSpeed model loading # + ######################################## + ######################################## + # If the model supports CUDA and DeepSpeed, this is where you can handle re-loading + # the model as/when DeepSpeed is enabled/selected in the user interface. If your + # TTS model doesnt support DeepSpeed, then it should be globally set in your + # model_settings.JSON and this function will never get called, however it still needs + # to exist as a function. + async def handle_deepspeed_change(self, value): + if value: + # DeepSpeed enabled + print(f"[{self.branding}ENG] \033[93mDeepSpeed Activating\033[0m") + await self.unload_model() + self.params["tts_method_api_local"] = True + self.deepspeed_enabled = True + await self.setup() + else: + # DeepSpeed disabled + print(f"[{self.branding}ENG] \033[93mDeepSpeed De-Activating\033[0m") + self.deepspeed_enabled = False + await self.unload_model() + await self.setup() + return value # Return new checkbox value + + ##################################################################################### + ##################################################################################### + # CHANGE ME # scan for available models/voices that are relevant to this TTS engine # + ##################################################################################### + ##################################################################################### + # This function looks and reports back the list of possible models your TTS engine can + # load in. Some TTS engines have multiple models they can load and you will want to use + # code for checking if the models are in the correct location/placement within the disk + # the correct files exst per model etc (check XTTS for an example of this). Some models + # like Piper, the actual models are the voices, so in the Piper scan_models_folder + # function, we fake the only model being available as {'piper': 'piper'} aka, model name + # then engine name, then we use the voices_file_list to populate the models as available + # voices that can be selected in the interface. + # If no models are found, we return "No Models Available" and continue on with the script. + def scan_models_folder(self): + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + + + + + + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + if not self.available_models: + self.available_models["No Models Found"] = "No Models Found" # Return a list with {'No Models Found'} if there are no models found. + return self.available_models # Return a list of models in the format {'engine name': 'model 1', 'engine name': 'model 2", etc} + + ############################################################# + ############################################################# + # CHANGE ME # POPULATE FILES LIST FROM VOICES DIRECTORY #### + ############################################################# + ############################################################# + # This function looks and reports back the list of possible voice your TTS engine can + # load in. Some TTS engines the voices are wav file samples (XTTS), some are models + # (Piper) and some are text (Parler) thats stored in a JSON file. We just need to + # populate the "voices" variable somehow and if no voices are found, we return + # "No Voices Found" back to the interface/api. + def voices_file_list(self): + try: + voices = [] + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + + + + + + + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + if not voices: + return ["No Voices Found"] # Return a list with {'No Voices Found'} if there are no voices/voice models. + return voices # Return a list of models in the format {'engine name': 'model 1', 'engine name': 'model 2", etc} + except Exception as e: + print(f"[{self.branding}ENG] \033[91mError\033[0m: Voices/Voice Models not found. Cannot load a list of voices.") + print(f"[{self.branding}ENG]") + return ["No Voices Found"] + + ################################################################################# + ################################################################################# + # CHANGE ME # Model loading # Piper does not actually load/stay resident in RAM # + ################################################################################# + ################################################################################# + # This function will handle the loading of your model, into VRAM/CUDA, System RAM or whatever. + # In XTTS, which has 2x model loader types, there are 2x loaders. They are called by "def handle_tts_method_change" + # In Piper we fake a model loader as Piper doesnt actually load a model into CUDA/System RAM as such. So, in that + # situation, api_manual_load_model is kind of a blank function. Though we do set self.is_tts_model_loaded = True + # as this is used elsewhere in the scripts to confirm that a model is available to be used for TTS generation. + # We always check for "No Models Available" being sent as that means we are trying to load in a model that + # doesnt exist/wasnt found on script start-up e.g. someone deleted the model from the folder or something. + async def api_manual_load_model(self, model_name): + if model_name == "No Models Found": + print(f"[{self.branding}ENG] \033[91mError\033[0m: No models for this TTS engine were found to load.") + raise HTTPException(status_code=400, detail="No models for this TTS engine were found to load.") + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + + + + + + + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + self.is_tts_model_loaded = True + return None + + ############################### + ############################### + # CHANGE ME # Model unloading # + ############################### + ############################### + # This function will handle the UN-loading of your model, from VRAM/CUDA, System RAM or whatever. + # In XTTS, that model loads into CUDA/System Ram, so when we swap models, we want to unload the current model + # free up the memory and then load in the new model to VRAM/CUDA. On the flip side of that, Piper doesnt + # doesnt load into memory, so we just need to put a fake function here that doesnt really do anything + # other than set "self.is_tts_model_loaded = False", which would be set back to true by the model loader. + # So look at the Piper model_engine.py if you DONT need to unload models. + async def unload_model(self): + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + + + + + + + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + self.is_tts_model_loaded = False + return None + + ################################################################################################################################### + ################################################################################################################################### + # CHANGE ME # Model changing. Unload out old model and load in a new one # XTTS is very unusal as it has 2x model loading methods # + ################################################################################################################################### + ################################################################################################################################### + # This function is your central model loading/unloading handler that deals with the above functions as necesary, to call loading, unloading, + # swappng DeepSpeed, Low vram etc. This function gets called with a "engine name - model name" type call. In XTTS, because there are 2x + # model loader types, (XTTS and APILocal), we take tts_method and split the "engine name - model name" into a loader type and the model + # that it needs to load in and then we call the correct loader function. Whereas in Piper, which doesnt load models into memory at all, + # we just have a fake function that doesnt really do anything. We always check to see if the model name has "No Models Available" in the + # name thats sent over, just to catch any potential errors. We display the start load time and end load time. Thats about it. + async def handle_tts_method_change(self, tts_method): + generate_start_time = time.time() # Record the start time of loading the model + if "No Models Available" in self.available_models: + print(f"[{self.branding}ENG] \033[91mError\033[0m: No models for this TTS engine were found to load. Please download a model.") + return False + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + + + + + + + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + generate_end_time = time.time() # Create an end timer for calculating load times + generate_elapsed_time = generate_end_time - generate_start_time # Calculate start time minus end time + print(f"[{self.branding}ENG] \033[94mLoad time :\033[93m {generate_elapsed_time:.2f} seconds.\033[0m") # Print out the result of the load time + return True #You need to return a True or False statement, based on the outcome of loading your model in + + ########################################################################################################################################## + ########################################################################################################################################## + # CHANGE ME # Model changing. Unload out old model and load in a new one # XTTS is very unusal as it has 2x model TTS generation methods # + ########################################################################################################################################## + ########################################################################################################################################## + # In here all the possible options are sent over (text, voice to use, lanugage, speed etc etc) and its up to you how you use them, or not. + # obviously if your TTS engine doesnt support speed for example, generationspeed_capable should be set False in your model_settings.JSON file + # and a fake "generationspeed_set" value should be set. This allows a fake value to be sent over from the main script, even though it + # wouldnt actually ever be used in the generation below. Nonethless all these values, real or just whats inside the configuration file + # will be sent over for use. + # Setting the xxxxxxx_capabale in the model_settings.JSON file, will enable/disable them being selectable by the user. For example, if you + # set "generationspeed_capable" as false in the model_settings.JSON file, a user will not be able to select OR set the setting for + # generation speed. + # One thing to note is that we HAVE to have something in this generation request that is synchronous from the way its called, which means + # we have to have an option for Streaming, even if our TTS engine doesnt support streaming. So in that case, we would set streaming_capable + # as false in our model_settings.JSON file, meaning streaming will never be called. However, we have to put a fake streaming routine in our + # function below (or a real function if it does support streaming of course). Parler has an example of a fake streaming function, which is + # very clearly highlighted in its model_engine.py script. + # Piper TTS, which uses command line based calls and therefore has different ones for Windows and Linux/Mac, has an example of doing this + # within its model_engine.py file. + async def generate_tts(self, text, voice, language, temperature, repetition_penalty, speed, pitch, output_file, streaming): + if voice == "No Voices Found": + print(f"[{self.branding}ENG] \033[91mError\033[0m: No voices found to generate TTS.") + raise HTTPException(status_code=400, detail="No voices found to generate TTS.") + if not self.is_tts_model_loaded: + print(f"[{self.branding}ENG] \033[91mError\033[0m: You currently have no TTS model loaded.") + raise HTTPException(status_code=400, detail="You currently have no TTS model loaded.") + self.tts_generating_lock = True # Lock the process to say its currently in use + generate_start_time = time.time() + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + + + + + + + + + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + generate_end_time = time.time() + generate_elapsed_time = generate_end_time - generate_start_time + print(f"[{self.branding}GEN] \033[94mTTS Generate: \033[93m{generate_elapsed_time:.2f} seconds. \033[94mLowVRAM: \033[33m{self.lowvram_enabled} \033[94mDeepSpeed: \033[33m{self.deepspeed_enabled}\033[0m") + self.tts_generating_lock = False diff --git a/system/tts_engines/template-tts-engine/model_settings.json b/system/tts_engines/template-tts-engine/model_settings.json new file mode 100644 index 0000000..a8674dc --- /dev/null +++ b/system/tts_engines/template-tts-engine/model_settings.json @@ -0,0 +1,42 @@ +{ + "model_details": { + "manufacturer_name": "MyTTSEngine", + "manufacturer_website": "https://github.com/MyTTSEngine", + "model_description": "A description and detail about the TTS Engine" + }, + "model_capabilties": { + "audio_format": "wav", + "deepspeed_capable": false, + "generationspeed_capable": false, + "languages_capable": false, + "lowvram_capable": false, + "multimodel_capable": false, + "repetitionpenalty_capable": false, + "streaming_capable": false, + "temperature_capable": false, + "multivoice_capable": false, + "pitch_capable": false, + "windows_capable": true, + "linux_capable": true, + "mac_capable": true + }, + "settings": { + "def_character_voice": "defaultvoice.wav", + "def_narrator_voice": "defaultvoice.wav", + "deepspeed_enabled": false, + "engine_installed": true, + "generationspeed_set": 1, + "lowvram_enabled": false, + "pitch_set": 0, + "repetitionpenalty_set": 10, + "temperature_set": 0.75 + }, + "openai_voices": { + "alloy": "defaultvoice.wav", + "echo": "defaultvoice.wav", + "fable": "defaultvoice.wav", + "nova": "defaultvoice.wav", + "onyx": "defaultvoice.wav", + "shimmer": "defaultvoice.wav" + } +} diff --git a/system/tts_engines/template-tts-engine/modelname_settings_page.py b/system/tts_engines/template-tts-engine/modelname_settings_page.py new file mode 100644 index 0000000..7d5fb28 --- /dev/null +++ b/system/tts_engines/template-tts-engine/modelname_settings_page.py @@ -0,0 +1,308 @@ +import os +import json +import requests +import gradio as gr +from tqdm import tqdm +from pathlib import Path +this_dir = Path(__file__).parent.resolve() # Sets up self.this_dir as a variable for the folder THIS script is running in. +main_dir = Path(__file__).parent.parent.parent.parent.resolve() # Sets up self.main_dir as a variable for the folder AllTalk is running in + +########################################################################## +# REQUIRED CHANGE # +# Populate the voices list, using the method specific to your TTS engine # +########################################################################## +# This function is responsible for populating the list of available voices for your TTS engine. +# You need to modify this function to use the appropriate method for your engine to retrieve the voice list. +# +# The current implementation lists all the WAV files in a "voices" directory, which may not be suitable for your engine. +# You should find/replace the `xxxx_voices_file_list` function name to match your engine name. For example, if your engine +# is named "mytts", the function should be named `mytts_voices_file_list`. +# +# You can pretty much find/replace "myttsengine" with the name of your TTS engine (about 14 entries in here) +# +# You will also neef to update the code with your own implementation that retrieves the voice list according to your +# engine's specific requirements. Typically this is the same code as will be in your model_engine.py file. +# +# For example, if your engine has a dedicated API or configuration file for managing voices, you should modify this +# function to interact with that API or read from that configuration file. +# +# After making the necessary changes, this function should return a list of available voices that can be used +# in your TTS engine's settings page. +def myttsengine_voices_file_list(): + voices = [] + models_dir = main_dir / "models" / "myttsengine" + + # Basically the same way you are finding/reporting available voices in your model_engine.py file + + return voices + +###################################################### +# REQUIRED CHANGE # +# Imports and saves the TTS engine-specific settings # +###################################################### +# This function is responsible for importing and saving the settings specific to your TTS engine. +# You need to make the following change: +# +# 1. Change the name of the function `xxxx_model_update_settings` to match your engine's name. +# For example, if your engine is named "mytts", the function should be named `mytts_model_update_settings`. +# +# After making this change, the function will load the model settings from a JSON file, update the settings and voice +# dictionaries with the values provided as arguments, and save the updated settings back to the JSON file. +# +# You do not need to modify the function's logic or any other part of the code. +def myttsengine_model_update_settings(def_character_voice_gr, def_narrator_voice_gr, lowvram_enabled_gr, deepspeed_enabled_gr, temperature_set_gr, repetitionpenalty_set_gr, pitch_set_gr, generationspeed_set_gr, alloy_gr, echo_gr, fable_gr, nova_gr, onyx_gr, shimmer_gr): + # Load the model_config_data from the JSON file + with open(os.path.join(this_dir, "model_settings.json"), "r") as f: + model_config_data = json.load(f) + # Update the settings and openai_voices dictionaries with the new values + model_config_data["settings"]["def_character_voice"] = def_character_voice_gr + model_config_data["settings"]["def_narrator_voice"] = def_narrator_voice_gr + model_config_data["openai_voices"]["alloy"] = alloy_gr + model_config_data["openai_voices"]["echo"] = echo_gr + model_config_data["openai_voices"]["fable"] = fable_gr + model_config_data["openai_voices"]["nova"] = nova_gr + model_config_data["openai_voices"]["onyx"] = onyx_gr + model_config_data["openai_voices"]["shimmer"] = shimmer_gr + model_config_data["settings"]["lowvram_enabled"] = lowvram_enabled_gr == "Enabled" + model_config_data["settings"]["deepspeed_enabled"] = deepspeed_enabled_gr == "Enabled" + model_config_data["settings"]["temperature_set"] = temperature_set_gr + model_config_data["settings"]["repetitionpenalty_set"] = repetitionpenalty_set_gr + model_config_data["settings"]["pitch_set"] = pitch_set_gr + model_config_data["settings"]["generationspeed_set"] = generationspeed_set_gr + # Save the updated model_config_data to the JSON file + with open(os.path.join(this_dir, "model_settings.json"), "w") as f: + json.dump(model_config_data, f, indent=4) + return "Settings updated successfully!" + +####################################################### +# REQUIRED CHANGE # +# Sets up the engine-specific settings page in Gradio # +####################################################### +# This function sets up the Gradio interface for the settings page specific to your TTS engine. +# You need to make the following changes: +# +# 1. Change the name of the function `xxxx_model_alltalk_settings` to match your engine's name. +# For example, if your engine is named "mytts", the function should be named `mytts_model_alltalk_settings`. +# +# 2. Change the name of the `submit_button.click` function call to match the name you gave to the function +# that imports and saves your engine's settings (the function you modified above). +# +# 3. Change the name of the `voice_list` function call to match the name of the function that lists +# the available voices for your TTS engine. +# +# 4. Change the 'title' of the `gr.Blocks` to match your engine's name e.g. title="mytts TTS" +# +# After making these changes, this function will create and return the Gradio interface for your TTS engine's +# settings page, allowing users to configure various options and voice selections. +def myttsengine_model_alltalk_settings(model_config_data): + features_list = model_config_data['model_capabilties'] + voice_list = myttsengine_voices_file_list() + with gr.Blocks(title="myttsengine TTS") as app: + with gr.Tab("Default Settings"): + with gr.Row(): + lowvram_enabled_gr = gr.Radio(choices={"Enabled": "true", "Disabled": "false"}, label="Low VRAM" if model_config_data["model_capabilties"]["lowvram_capable"] else "Low VRAM N/A", value="Enabled" if model_config_data["settings"]["lowvram_enabled"] else "Disabled", interactive=model_config_data["model_capabilties"]["lowvram_capable"]) + deepspeed_enabled_gr = gr.Radio(choices={"Enabled": "true", "Disabled": "false"}, label="DeepSpeed Activate" if model_config_data["model_capabilties"]["deepspeed_capable"] else "DeepSpeed N/A", value="Enabled" if model_config_data["settings"]["deepspeed_enabled"] else "Disabled", interactive=model_config_data["model_capabilties"]["deepspeed_capable"]) + temperature_set_gr = gr.Slider(value=float(model_config_data["settings"]["temperature_set"]), minimum=0, maximum=1, step=0.05, label="Temperature" if model_config_data["model_capabilties"]["temperature_capable"] else "Temperature N/A", interactive=model_config_data["model_capabilties"]["temperature_capable"]) + repetitionpenalty_set_gr = gr.Slider(value=float(model_config_data["settings"]["repetitionpenalty_set"]), minimum=1, maximum=20, step=1, label="Repetition Penalty" if model_config_data["model_capabilties"]["repetitionpenalty_capable"] else "Repetition N/A", interactive=model_config_data["model_capabilties"]["repetitionpenalty_capable"]) + pitch_set_gr = gr.Slider(value=float(model_config_data["settings"]["pitch_set"]), minimum=-10, maximum=10, step=1, label="Pitch" if model_config_data["model_capabilties"]["pitch_capable"] else "Pitch N/A", interactive=model_config_data["model_capabilties"]["pitch_capable"]) + generationspeed_set_gr = gr.Slider(value=float(model_config_data["settings"]["generationspeed_set"]), minimum=0.25, maximum=2.00, step=0.25, label="Speed" if model_config_data["model_capabilties"]["generationspeed_capable"] else "Speed N/A", interactive=model_config_data["model_capabilties"]["generationspeed_capable"]) + with gr.Row(): + with gr.Column(): + gr.Markdown("### OpenAI Voice Mappings") + with gr.Group(): + with gr.Row(): + alloy_gr = gr.Dropdown(value=model_config_data["openai_voices"]["alloy"], label="Alloy", choices=voice_list, allow_custom_value=True) + echo_gr = gr.Dropdown(value=model_config_data["openai_voices"]["echo"], label="Echo", choices=voice_list, allow_custom_value=True) + with gr.Row(): + fable_gr = gr.Dropdown(value=model_config_data["openai_voices"]["fable"], label="Fable", choices=voice_list, allow_custom_value=True) + nova_gr = gr.Dropdown(value=model_config_data["openai_voices"]["nova"], label="Nova", choices=voice_list, allow_custom_value=True) + with gr.Row(): + onyx_gr = gr.Dropdown(value=model_config_data["openai_voices"]["onyx"], label="Onyx", choices=voice_list, allow_custom_value=True) + shimmer_gr = gr.Dropdown(value=model_config_data["openai_voices"]["shimmer"], label="Shimmer", choices=voice_list, allow_custom_value=True) + with gr.Column(): + gr.Markdown("### Default Voices") + with gr.Row(): + def_character_voice_gr = gr.Dropdown(value=model_config_data["settings"]["def_character_voice"], label="Default/Character Voice", choices=voice_list, allow_custom_value=True) + def_narrator_voice_gr = gr.Dropdown(value=model_config_data["settings"]["def_narrator_voice"], label="Narrator Voice", choices=voice_list, allow_custom_value=True) + with gr.Group(): + with gr.Row(): + details_text = gr.Textbox(label="Details", show_label=False, lines=5, interactive=False, value="In this section, you can set the default settings for this TTS engine. Settings that are not supported by the current engine will be greyed out and cannot be selected. Default voices specified here will be used when no specific voice is provided in the TTS generation request. If a voice is specified in the request, it will override these default settings. When using the OpenAI API compatable API with this TTS engine, the voice mappings will be applied. As the OpenAI API has a limited set of 6 voices, these mappings ensure compatibility by mapping the OpenAI voices to the available voices in this TTS engine.") + with gr.Row(): + submit_button = gr.Button("Update Settings") + output_message = gr.Textbox(label="Output Message", interactive=False, show_label=False) + submit_button.click(myttsengine_model_update_settings, inputs=[def_character_voice_gr, def_narrator_voice_gr, lowvram_enabled_gr, deepspeed_enabled_gr, temperature_set_gr, repetitionpenalty_set_gr, pitch_set_gr, generationspeed_set_gr, alloy_gr, echo_gr, fable_gr, nova_gr, onyx_gr, shimmer_gr], outputs=output_message) + + ########################################################################################### + # Do not change this section apart from "TTS Engine Name" value to match your engine name # + ########################################################################################### + with gr.Tab("Engine Information"): + with gr.Row(): + with gr.Group(): + gr.Textbox(label="Manufacturer Name", value=model_config_data['model_details']['manufacturer_name'], interactive=False) + gr.Textbox(label="Manufacturer Website/TTS Engine Support", value=model_config_data['model_details']['manufacturer_website'], interactive=False) + gr.Textbox(label="Engine/Model Description", value=model_config_data['model_details']['model_description'], interactive=False, lines=13) + with gr.Column(): + with gr.Row(): + gr.Textbox(label="DeepSpeed Capable", value='Yes' if features_list['deepspeed_capable'] else 'No', interactive=False) + gr.Textbox(label="Pitch Capable", value='Yes' if features_list['pitch_capable'] else 'No', interactive=False) + gr.Textbox(label="Generation Speed Capable", value='Yes' if features_list['generationspeed_capable'] else 'No', interactive=False) + with gr.Row(): + gr.Textbox(label="Repetition Penalty Capable", value='Yes' if features_list['repetitionpenalty_capable'] else 'No', interactive=False) + gr.Textbox(label="Multi Languages Capable", value='Yes' if features_list['languages_capable'] else 'No', interactive=False) + gr.Textbox(label="Streaming Capable", value='Yes' if features_list['streaming_capable'] else 'No', interactive=False) + with gr.Row(): + gr.Textbox(label="Low VRAM Capable", value='Yes' if features_list['lowvram_capable'] else 'No', interactive=False) + gr.Textbox(label="Temperature Capable", value='Yes' if features_list['temperature_capable'] else 'No', interactive=False) + gr.Textbox(label="Multi Model Capable Engine", value='Yes' if features_list['multimodel_capable'] else 'No', interactive=False) + with gr.Row(): + gr.Textbox(label="Multi Voice Capable Models", value='Yes' if features_list['multivoice_capable'] else 'No', interactive=False) + gr.Textbox(label="Default Audio output format", value=model_config_data['model_capabilties']['audio_format'], interactive=False) + gr.Textbox(label="TTS Engine Name", value="myttsengine", interactive=False) + with gr.Row(): + gr.Textbox(label="Windows Support", value='Yes' if features_list['windows_capable'] else 'No', interactive=False) + gr.Textbox(label="Linux Support", value='Yes' if features_list['linux_capable'] else 'No', interactive=False) + gr.Textbox(label="Mac Support", value='Yes' if features_list['mac_capable'] else 'No', interactive=False) + with gr.Row(): + gr.Markdown(""" + #### 🟧 DeepSpeed Capable + DeepSpeed is a deep learning optimization library that can significantly speed up model training and inference. If a model is DeepSpeed capable, it means it can utilize DeepSpeed to accelerate the generation of text-to-speech output. This requires the model to be loaded into CUDA/VRAM on an Nvidia GPU and the model's inference method to support DeepSpeed. + #### 🟧 Pitch Capable + Pitch refers to the highness or lowness of a sound. If a model is pitch capable, it can adjust the pitch of the generated speech, allowing for more expressive and varied output. + #### 🟧 Generation Speed Capable + Generation speed refers to the rate at which the model can generate text-to-speech output. If a model is generation speed capable, it means the speed of the generated speech can be adjusted, making it faster or slower depending on the desired output. + #### 🟧 Repetition Penalty Capable + Repetition penalty is a technique used to discourage the model from repeating the same words or phrases multiple times in the same sounding way. If a model is repetition penalty capable, it can apply this penalty during generation to improve the diversity and naturalness of the output. + #### 🟧 Multi-Languages Capable + Multi-language capable models can generate speech in multiple languages. This means that the model has been trained on data from different languages and can switch between them during generation. Some models are language-specific. + #### 🟧 Multi-Voice Capable + Multi-voice capable models generate speech in multiple voices or speaking styles. This means that the model has been trained on data from different speakers and can mimic their voices during generation, or is a voice cloning model that can generate speech based on the input sample. + """) + gr.Markdown(""" + #### 🟧 Streaming Capable + Streaming refers to the ability to generate speech output in real-time, without the need to generate the entire output before playback. If a model is streaming capable, it can generate speech on-the-fly, allowing for faster response times and more interactive applications. + #### 🟧 Low VRAM Capable + VRAM (Video Random Access Memory) is a type of memory used by GPUs to store and process data. If a model is low VRAM capable, it can efficiently utilize the available VRAM by moving data between CPU and GPU memory as needed, allowing for generation even on systems with limited VRAM where it may be competing with an LLM model for VRAM. + #### 🟧 Temperature Capable + Temperature is a hyperparameter that controls the randomness of the generated output. If a model is temperature capable, the temperature can be adjusted to make the output more or less random, affecting the creativity and variability of the generated speech. + #### 🟧 Multi-Model Capable Engine + If an engine is multi-model capable, it means that it can support and utilize multiple models for text-to-speech generation. This allows for greater flexibility and the ability to switch between different models depending on the desired output. Different models may be capable of different languages, specific languages, voices, etc. + #### 🟧 Default Audio Output Format + Specifies the file format in which the generated speech will be saved. Common audio formats include WAV, MP3, FLAC, Opus, AAC, and PCM. If you want different outputs, you can set the transcode function to change the output audio, though transcoding will add a little time to the generation and is not available for streaming generation. + #### 🟧 Windows/Linux/Mac Support + These indicators show whether the model and engine are compatible with Windows, Linux, or macOS. However, additional setup or requirements may be necessary to ensure full functionality on your operating system. Please note that full platform support has not been extensively tested. + """) + + ####################################################################################################################################################################################################### + # REQUIRED CHANGE # + # You will need to build a custom method to identify if your models are installed and download them. Store your models list in the available_models.json file, alongside your model_settings.json etc # + ####################################################################################################################################################################################################### + with gr.Tab("Models/Voices Download"): + with gr.Row(): + # Load the available models from the JSON file + with open(os.path.join(this_dir, "available_models.json"), "r") as f: + available_models = json.load(f) + # Extract the model names for the dropdown + model_names = [model["model_name"] for model in available_models["models"]] + # Create the dropdown + model_dropdown = gr.Dropdown(choices=sorted(model_names), label="Select Model", value=model_names[0]) + download_button = gr.Button("Download Model/Missing Files") + + def download_model(model_name, force_download=False): + # Find the selected model in the available models + selected_model = next(model for model in available_models["models"] if model["model_name"] == model_name) + # Get the folder path and files to download + folder_path = os.path.join(main_dir, "models", "myttsengine") + files_to_download = selected_model["files_to_download"] + # Check if all files are already downloaded + all_files_exists = all(os.path.exists(os.path.join(folder_path, os.path.basename(url.split('?')[0]))) for url in files_to_download) + if all_files_exists and not force_download: + return "All files are already downloaded. No need to download again." + else: + # Create the folder if it doesn't exist + os.makedirs(folder_path, exist_ok=True) + # Download the missing files + for url in files_to_download: + file_name = os.path.basename(url.split('?')[0]) + file_path = os.path.join(folder_path, file_name) + if not os.path.exists(file_path) or force_download: + print(f"Downloading {file_name}...") + response = requests.get(url, stream=True) + total_size_in_bytes = int(response.headers.get("content-length", 0)) + block_size = 1024 # 1 Kibibyte + progress_bar = tqdm(total=total_size_in_bytes, unit="iB", unit_scale=True) + with open(file_path, "wb") as file: + for data in response.iter_content(block_size): + progress_bar.update(len(data)) + file.write(data) + progress_bar.close() + return "Model downloaded successfully!" + + with gr.Row(): + download_status = gr.Textbox(label="Download Status") + + download_button.click(download_model, inputs=model_dropdown, outputs=download_status) + + def show_confirm_cancel(model_name): + all_files_exists = all(os.path.exists(os.path.join(main_dir, "models", "myttsengine", os.path.basename(file.split('?')[0]))) for model in available_models["models"] if model["model_name"] == model_name for file in model["files_to_download"]) + if all_files_exists: + return [gr.update(visible=True), gr.update(visible=True), gr.update(visible=True)] + else: + return [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)] + + def confirm_download(model_name): + download_status_text = download_model(model_name, force_download=True) + return [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), download_status_text] + + def cancel_download(): + return [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), ""] + + with gr.Row(): + confirm_button = gr.Button("Download Anyway", visible=False) + cancel_button = gr.Button("Cancel", visible=False) + + download_button.click(show_confirm_cancel, inputs=model_dropdown, outputs=[confirm_button, download_button, cancel_button]) + confirm_button.click(confirm_download, inputs=model_dropdown, outputs=[confirm_button, download_button, cancel_button, download_status]) + cancel_button.click(cancel_download, inputs=None, outputs=[confirm_button, download_button, cancel_button, download_status]) + + ################################################################################################### + # REQUIRED CHANGE # + # Add any engine specific help, bugs, issues, operating system specifc requirements/setup in here # + # Please use Markdown format, so gr.Markdown() with your markdown inside it. # + ################################################################################################### + with gr.Tab("Engine Help"): + with gr.Row(): + using_voice_samples = """ + + + IF YOU WANT TO ADD HELP INSTRUCTIONS FOR THE MODEL, ADD THEM IN HERE USING MARKDOWN + + + """ + gr.Markdown(using_voice_samples) + + return app + +################################ +# REQUIRED CHANGE # +# Sets up the Gradio interface # +################################ +# This function sets up the Gradio interface for your TTS engine's settings page. +# You need to change the name of the function calls to match the names you set in the functions above. +# +# Specifically, you need to update the following: +# +# 1. The name of the function `xxxx_at_gradio_settings_page` to match your engine's name. +# For example, if your engine is named "mytts", the function should be named `mytts_at_gradio_settings_page`. +# +# 2. The name of the function call `xxxx_model_alltalk_settings(model_config_data)`. +# This should match the name you gave to the function that sets up the engine-specific settings page in Gradio. +# If you named that function `mytts_model_alltalk_settings`, then the call should be: +# `mytts_model_alltalk_settings(model_config_data)` +# +# After making these changes, this function will create and return the Gradio app for your TTS engine's settings page. +def myttsengine_at_gradio_settings_page(model_config_data): + app = myttsengine_model_alltalk_settings(model_config_data) + return app +def myttsengine_at_gradio_settings_page(model_config_data): + app = myttsengine_model_alltalk_settings(model_config_data) + return app \ No newline at end of file diff --git a/system/tts_engines/vits/available_models.json b/system/tts_engines/vits/available_models.json new file mode 100644 index 0000000..6736a95 --- /dev/null +++ b/system/tts_engines/vits/available_models.json @@ -0,0 +1,190 @@ +{ + "first_start_model": "tts_models--en--vctk--vits", + "models": [ + { + "model_name": "tts_models--bg--cv--vits", + "folder_path": "tts_models--bg--cv--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--bg--cv--vits.zip" + }, + { + "model_name": "tts_models--cs--cv--vits", + "folder_path": "tts_models--cs--cv--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--cs--cv--vits.zip" + }, + { + "model_name": "tts_models--da--cv--vits", + "folder_path": "tts_models--da--cv--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--da--cv--vits.zip" + }, + { + "model_name": "tts_models--et--cv--vits", + "folder_path": "tts_models--et--cv--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--et--cv--vits.zip" + }, + { + "model_name": "tts_models--ga--cv--vits", + "folder_path": "tts_models--ga--cv--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--ga--cv--vits.zip" + }, + { + "model_name": "tts_models--en--vctk--vits", + "folder_path": "tts_models--en--vctk--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.6.1_models/tts_models--en--vctk--vits.zip" + }, + { + "model_name": "tts_models--es--css10--vits", + "folder_path": "tts_models--es--css10--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--es--css10--vits.zip" + }, + { + "model_name": "tts_models--fr--css10--vits", + "folder_path": "tts_models--fr--css10--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--fr--css10--vits.zip" + }, + { + "model_name": "tts_models--uk--mai--vits", + "folder_path": "tts_models--uk--mai--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--uk--mai--vits.zip" + }, + { + "model_name": "tts_models--nl--css10--vits", + "folder_path": "tts_models--nl--css10--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--nl--css10--vits.zip" + }, + { + "model_name": "tts_models--de--thorsten--vits", + "folder_path": "tts_models--de--thorsten--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.7.0_models/tts_models--de--thorsten--vits.zip" + }, + { + "model_name": "tts_models--de--css10--vits-neon", + "folder_path": "tts_models--de--css10--vits-neon", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--de--css10--vits.zip" + }, + { + "model_name": "tts_models--it--mai_female--vits", + "folder_path": "tts_models--it--mai_female--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.6.1_models/tts_models--it--mai_female--vits.zip" + }, + { + "model_name": "tts_models--it--mai_male--vits", + "folder_path": "tts_models--it--mai_male--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.6.1_models/tts_models--it--mai_male--vits.zip" + }, + { + "model_name": "tts_models--ewe--openbible--vits", + "folder_path": "tts_models--ewe--openbible--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.6.2_models/tts_models--ewe--openbible--vits.zip" + }, + { + "model_name": "tts_models--hau--openbible--vits", + "folder_path": "tts_models--hau--openbible--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.6.2_models/tts_models--hau--openbible--vits.zip" + }, + { + "model_name": "tts_models--lin--openbible--vits", + "folder_path": "tts_models--lin--openbible--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.6.2_models/tts_models--lin--openbible--vits.zip" + }, + { + "model_name": "tts_models--tw_akuapem--openbible--vits", + "folder_path": "tts_models--tw_akuapem--openbible--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.6.2_models/tts_models--tw_akuapem--openbible--vits.zip" + }, + { + "model_name": "tts_models--tw_asante--openbible--vits", + "folder_path": "tts_models--tw_asante--openbible--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.6.2_models/tts_models--tw_asante--openbible--vits.zip" + }, + { + "model_name": "tts_models--yor--openbible--vits", + "folder_path": "tts_models--yor--openbible--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.6.2_models/tts_models--yor--openbible--vits.zip" + }, + { + "model_name": "tts_models--hu--css10--vits", + "folder_path": "tts_models--hu--css10--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--hu--css10--vits.zip" + }, + { + "model_name": "tts_models--el--cv--vits", + "folder_path": "tts_models--el--cv--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--el--cv--vits.zip" + }, + { + "model_name": "tts_models--fi--css10--vits", + "folder_path": "tts_models--fi--css10--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--fi--css10--vits.zip" + }, + { + "model_name": "tts_models--hr--cv--vits", + "folder_path": "tts_models--hr--cv--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--hr--cv--vits.zip" + }, + { + "model_name": "tts_models--lt--cv--vits", + "folder_path": "tts_models--lt--cv--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--lt--cv--vits.zip" + }, + { + "model_name": "tts_models--lv--cv--vits", + "folder_path": "tts_models--lv--cv--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--lv--cv--vits.zip" + }, + { + "model_name": "tts_models--mt--cv--vits", + "folder_path": "tts_models--mt--cv--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--mt--cv--vits.zip" + }, + { + "model_name": "tts_models--pl--mai_female--vits", + "folder_path": "tts_models--pl--mai_female--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--pl--mai_female--vits.zip" + }, + { + "model_name": "tts_models--pt--cv--vits", + "folder_path": "tts_models--pt--cv--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--pt--cv--vits.zip" + }, + { + "model_name": "tts_models--ro--cv--vits", + "folder_path": "tts_models--ro--cv--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--ro--cv--vits.zip" + }, + { + "model_name": "tts_models--sk--cv--vits", + "folder_path": "tts_models--sk--cv--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--sk--cv--vits.zip" + }, + { + "model_name": "tts_models--sl--cv--vits", + "folder_path": "tts_models--sl--cv--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--sl--cv--vits.zip" + }, + { + "model_name": "tts_models--sv--cv--vits", + "folder_path": "tts_models--sv--cv--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.8.0_models/tts_models--sv--cv--vits.zip" + }, + { + "model_name": "tts_models--ca--custom--vits", + "folder_path": "tts_models--ca--custom--vits", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.10.1_models/tts_models--ca--custom--vits.zip" + }, + { + "model_name": "tts_models--en--jenny--jenny", + "folder_path": "tts_models--en--jenny--jenny", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.14.0_models/tts_models--en--jenny--jenny.zip" + }, + { + "model_name": "tts_models--bn--custom--vits-male", + "folder_path": "tts_models--bn--custom--vits-male", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.13.3_models/tts_models--bn--custom--vits_male.zip" + }, + { + "model_name": "tts_models--bn--custom--vits-female", + "folder_path": "tts_models--bn--custom--vits-female", + "github_rls_url": "https://coqui.gateway.scarf.sh/v0.13.3_models/tts_models--bn--custom--vits_female.zip" + } + ] + } \ No newline at end of file diff --git a/system/tts_engines/vits/model_engine.py b/system/tts_engines/vits/model_engine.py new file mode 100644 index 0000000..cc020af --- /dev/null +++ b/system/tts_engines/vits/model_engine.py @@ -0,0 +1,548 @@ +############################################### +# DONT CHANGE # These are base imports needed # +############################################### +import os +import sys +import json +import time +import torch +import logging +from pathlib import Path +from fastapi import (HTTPException) +logging.disable(logging.WARNING) +############################################### +# DONT CHANGE # Get Pytorch & Python versions # +############################################### +pytorch_version = torch.__version__ +cuda_version = torch.version.cuda +major, minor, micro = sys.version_info[:3] +python_version = f"{major}.{minor}.{micro}" +try: + import deepspeed + deepspeed_available = True +except ImportError: + deepspeed_available = False + pass + +############################################################################################################# +############################################################################################################# +# CHANGE ME # Run any specifc imports, requirements or setup any global vaiables needed for this TTS Engine # +############################################################################################################# +############################################################################################################# +# In this section you will import any imports that your specific TTS Engine will use. You will provide any +# start-up errors for those bits, as if you were starting up a normal Python script. Note the logging.disable +# a few lines up from here, you may want to # that out while debugging! +try: + from TTS.utils.synthesizer import Synthesizer +except ModuleNotFoundError: + print( + f"[Startup] \033[91mWarning\033[0m Could not find the TTS module. Make sure to install the requirements for the alltalk_tts extension.", + f"[Startup] \033[91mWarning\033[0m Linux / Mac:\npip install -r extensions/alltalk_tts/requirements.txt\n", + f"[Startup] \033[91mWarning\033[0m Windows:\npip install -r extensions\\alltalk_tts\\requirements.txt\n", + f"[Startup] \033[91mWarning\033[0m If you used the one-click installer, paste the command above in the terminal window launched after running the cmd_ script. On Windows, that's cmd_windows.bat." + ) + raise + +############################################################# +# DONT CHANGE # Do not change the Class name from tts_class # +############################################################# +class tts_class: + def __init__(self): + ######################################################################## + # DONT CHANGE # Sets up the base variables required for any tts engine # + ######################################################################## + self.branding = None + self.this_dir = Path(__file__).parent.resolve() # Sets up self.this_dir as a variable for the folder THIS script is running in. + self.main_dir = Path(__file__).parent.parent.parent.parent.resolve() # Sets up self.main_dir as a variable for the folder AllTalk is running in + self.device = "cuda" if torch.cuda.is_available() else "cpu" # Sets up self.device to cuda if torch exists with Nvidia/CUDA, otherwise sets to cpu + self.cuda_is_available = torch.cuda.is_available() # Sets up cuda_is_available as a True/False to track if Nvidia/CUDA was found on the system + self.tts_generating_lock = False # Used to lock and unlock the tts generation process at the start/end of tts generation. + self.tts_stop_generation = False # Used in conjunction with tts_generating_lock to call for a stop to the current generation. If called (set True) it needs to be set back to False when generation has been stopped. + self.tts_narrator_generatingtts = False # Used to track if the current tts processes is narrator based. This can be used in conjunction with lowvram and device to avoid moving model between GPU(CUDA)<>RAM(CPU) each chunk of narrated text generated. + self.model = None # If loading a model into CUDA/VRAM/RAM "model" is used as the variable name to load and interact with (see the XTTS model_engine script for examples.) + self.is_tts_model_loaded = False # Used to track if a model is actually loaded in and error/fail things like TTS generation if its False + self.current_model_loaded = None # Stores the name of the currenly loaded in model + self.available_models = None # List of available models found by "def scan_models_folder" + self.setup_has_run = False # Tracks if async def setup(self) has run, by setting to True, so that the /api/ready endpoint can provide a "Ready" status + ############################################################################################## + # DONT CHANGE # Load in a list of the available TTS engines and the currently set TTS engine # + ############################################################################################## + tts_engines_file = os.path.join(self.main_dir, "system", "tts_engines", "tts_engines.json") + with open(tts_engines_file, "r") as f: + tts_engines_data = json.load(f) + self.engines_available = [engine["name"] for engine in tts_engines_data["engines_available"]] # A list of ALL the TTS engines available to be loaded by AllTalk + self.engine_loaded = tts_engines_data["engine_loaded"] # In "tts_engines.json" what is the currently set TTS engine loading into AllTalk + self.selected_model = tts_engines_data["selected_model"] # In "tts_engines.json" what is the currently set TTS model loading into AllTalk + ############################################################################ + # DONT CHANGE # Pull out all the settings for the currently set TTS engine # + ############################################################################ + with open(os.path.join(self.this_dir, "model_settings.json"), "r") as f: + tts_model_loaded = json.load(f) + # Access the model details + self.manufacturer_name = tts_model_loaded["model_details"]["manufacturer_name"] # The company/person/body that generated the TTS engine/models etc + self.manufacturer_website = tts_model_loaded["model_details"]["manufacturer_website"] # The website of the company/person/body where people can find more information + # Access the features the model is capable of: + self.audio_format = tts_model_loaded["model_capabilties"]["audio_format"] # This details the audio format your TTS engine is set to generate TTS in e.g. wav, mp3, flac, opus, acc, pcm. Please use only 1x format. + self.deepspeed_capable = tts_model_loaded["model_capabilties"]["deepspeed_capable"] # Is your model capable of DeepSpeed + self.deepspeed_available = 'deepspeed' in globals() # When we did the import earlier, at the top of this script, was DeepSpeed available for use + self.generationspeed_capable = tts_model_loaded["model_capabilties"]["generationspeed_capable"] # Does this TTS engine support changing the speed of the generated TTS + self.languages_capable = tts_model_loaded["model_capabilties"]["languages_capable"] # Are the actual models themselves capable of generating in multiple languages OR is each model language specific + self.lowvram_capable = tts_model_loaded["model_capabilties"]["lowvram_capable"] # Is this engine capable of using low VRAM (moving the model between CPU And GPU memory) + self.multimodel_capable = tts_model_loaded["model_capabilties"]["multimodel_capable"] # Is there just the one model or are there multiple models this engine supports. + self.repetitionpenalty_capable = tts_model_loaded["model_capabilties"]["repetitionpenalty_capable"] # Is this TTS engine capable of changing the repititon penalty + self.streaming_capable = tts_model_loaded["model_capabilties"]["streaming_capable"] # Is this TTS engine capabale of generating streaming audio + self.temperature_capable = tts_model_loaded["model_capabilties"]["temperature_capable"] # Is this TTS engine capable of changing the temperature of the models + self.multivoice_capable = tts_model_loaded["model_capabilties"]["multivoice_capable"] # Are the models multi-voice or single vocice models + self.pitch_capable = tts_model_loaded["model_capabilties"]["pitch_capable"] # Is this TTS engine capable of changing the pitch of the genrated TTS + # Access the current enginesettings + self.def_character_voice = tts_model_loaded["settings"]["def_character_voice"] # What is the current default main/character voice that will be used if no voice specified. + self.def_narrator_voice = tts_model_loaded["settings"]["def_narrator_voice"] # What is the current default narrator voice that will be used if no voice specified. + self.deepspeed_enabled = tts_model_loaded["settings"]["deepspeed_enabled"] # If its available, is DeepSpeed enabled for the TTS engine + self.engine_installed = tts_model_loaded["settings"]["engine_installed"] # Has the TTS engine been setup/installed (not curently used) + self.generationspeed_set = tts_model_loaded["settings"]["generationspeed_set"] # What is the set/stored speed for generation. + self.lowvram_enabled = tts_model_loaded["settings"]["lowvram_enabled"] # If its available, is LowVRAM enabled for the TTS engine + # Check if someone has enabled lowvram on a system that's not CUDA enabled + self.lowvram_enabled = False if not torch.cuda.is_available() else self.lowvram_enabled # If LowVRAM is mistakenly set and CUDA is not available, this will force it back off + self.repetitionpenalty_set = tts_model_loaded["settings"]["repetitionpenalty_set"] # What is the currenly set repitition policy of the model (If it support repetition) + self.temperature_set = tts_model_loaded["settings"]["temperature_set"] # What is the currenly set temperature of the model (If it support temp) + self.pitch_set = tts_model_loaded["settings"]["pitch_set"] # What is the currenly set pitch of the model (If it support temp) + # Gather the OpenAI API Voice Mappings + self.openai_alloy = tts_model_loaded["openai_voices"]["alloy"] # The TTS engine voice that will be mapped to Open AI Alloy voice + self.openai_echo = tts_model_loaded["openai_voices"]["echo"] # The TTS engine voice that will be mapped to Open AI Echo voice + self.openai_fable = tts_model_loaded["openai_voices"]["fable"] # The TTS engine voice that will be mapped to Open AI Fable voice + self.openai_nova = tts_model_loaded["openai_voices"]["nova"] # The TTS engine voice that will be mapped to Open AI Nova voice + self.openai_onyx = tts_model_loaded["openai_voices"]["onyx"] # The TTS engine voice that will be mapped to Open AI Onyx voice + self.openai_shimmer = tts_model_loaded["openai_voices"]["shimmer"] # The TTS engine voice that will be mapped to Open AI Shimmer voice + ################################################################### + # DONT CHANGE # Load params and api_defaults from confignew.json # + ################################################################### + # Define the path to the confignew.json file + configfile_path = self.main_dir / "confignew.json" + # Load config file and get settings + with open(configfile_path, "r") as configfile: + configfile_data = json.load(configfile) + self.branding = configfile_data.get("branding", "") # Sets up self.branding for outputting the name stored in the "confgnew.json" file, as used in print statements. + self.params = configfile_data # Loads in the curent "confgnew.json" file to self.params. + self.debug_tts = configfile_data.get("debugging").get("debug_tts") # Can be used within this script as a True/False flag for generally debugging the TTS generation process. + self.debug_tts_variables = configfile_data.get("debugging").get("debug_tts_variables") # Can be used within this script as a True/False flag for generally debugging variables (if you wish). + ################################################################ + # DONT CHANGE # Print out Python, CUDA, DeepSpeed versions #### + ################################################################ + def printout_versions(self): + if deepspeed_available: + print(f"[{self.branding}ENG] \033[92mDeepSpeed version :\033[93m",deepspeed.__version__,"\033[0m") + else: + print(f"[{self.branding}ENG] \033[92mDeepSpeed version :\033[93m Not available\033[0m") + print(f"[{self.branding}ENG] \033[92mPython Version :\033[93m {python_version}\033[0m") + print(f"[{self.branding}ENG] \033[92mPyTorch Version :\033[93m {pytorch_version}\033[0m") + if cuda_version is None: + print(f"[{self.branding}ENG] \033[92mCUDA Version :\033[91m Not available\033[0m") + else: + print(f"[{self.branding}ENG] \033[92mCUDA Version :\033[93m {cuda_version}\033[0m") + print(f"[{self.branding}ENG]") + return + + ################################################################################### + ################################################################################### + # CHANGE ME # Inital setup of the model and engine. Called when the script starts # + ################################################################################### + ################################################################################### + # In here you will add code to load in your model and do its inital setup. So you + # may be calling your model loader via handle_tts_method_change or if your TTS + # engine doesnt actually load a model into CUDA or System RAM, you may be doing + # Something to fake its start-up. + async def setup(self): + self.printout_versions() + self.available_models = self.scan_models_folder() + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + print("in setup - self.selected_model:", self.selected_model) if self.debug_tts else None + if self.selected_model: + tts_model = f"{self.selected_model}" + print("in setup - tts_model:", tts_model) if self.debug_tts else None + if tts_model in self.available_models: + print("in setup - sending to handle_tts_method_change:") if self.debug_tts else None + await self.handle_tts_method_change(tts_model) + self.current_model_loaded = tts_model + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + else: + self.current_model_loaded = "No Models Available" + print(f"[{self.branding}ENG] \033[91mError\033[0m: Selected model '{self.selected_model}' not found in the models folder.") + self.setup_has_run = True # Set to True, so that the /api/ready endpoint can provide a "Ready" status + + ################################## + ################################## + # CHANGE ME # Low VRAM Swapping # + ################################## + ################################## + # If your model does load into CUDA and you want to support LowVRAM, aka moving the model + # on the fly between CUDA and System RAM on each generation request, you will be adding + # The code in here to do it. See the XTTS tts engine for an example. Piper however, doesnt + # Load into VRAM or System RAM, also low vram is set globally disabled by the model_settings.JSON + # file, so this would never get called anywyay, however, we still need to keep an empty + # function in place. Piper TTS uses "pass" telling function to just exit out cleanly if called. + # However, its quite a simple check along the lines of "if CUDA is available and model is + # in X place, then send it to Y place (or Y to X). + async def handle_lowvram_change(self): + if torch.cuda.is_available(): + if self.device == "cuda": + self.device = "cpu" + self.model.to(self.device) + torch.cuda.empty_cache() + else: + self.device == "cpu" + self.device = "cuda" + self.model.to(self.device) + + ######################################## + ######################################## + # CHANGE ME # DeepSpeed model loading # + ######################################## + ######################################## + # If the model supports CUDA and DeepSpeed, this is where you can handle re-loading + # the model as/when DeepSpeed is enabled/selected in the user interface. If your + # TTS model doesnt support DeepSpeed, then it should be globally set in your + # model_settings.JSON and this function will never get called, however it still needs + # to exist as a function. + async def handle_deepspeed_change(self, value): + if value: + # DeepSpeed enabled + print(f"[{self.branding}ENG] \033[93mDeepSpeed Activating\033[0m") + await self.unload_model() + self.params["tts_method_api_local"] = False + self.deepspeed_enabled = True + await self.setup() + else: + # DeepSpeed disabled + print(f"[{self.branding}ENG] \033[93mDeepSpeed De-Activating\033[0m") + self.deepspeed_enabled = False + await self.unload_model() + await self.setup() + return value # Return new checkbox value + + ############################################################################################################################################## + ############################################################################################################################################## + # CHANGE ME # scan for available models/voices that are relevant to this TTS engine # XTTS is very unusal as it has 2x model loading methods # + ############################################################################################################################################## + ############################################################################################################################################## + # This function looks and reports back the list of possible models your TTS engine can + # load in. Some TTS engines have multiple models they can load and you will want to use + # code for checking if the models are in the correct location/placement within the disk + # the correct files exst per model etc (check XTTS for an example of this). Some models + # like Piper, the actual models are the voices, so in the Piper scan_models_folder + # function, we fake the only model being available as {'piper': 'piper'} aka, model name + # then engine name, then we use the voices_file_list to populate the models as available + # voices that can be selected in the interface. + # If no models are found, we return "No Models Available" and continue on with the script. + def scan_models_folder(self): + models_folder = self.main_dir / "models" / "vits" # Edit to match the name of your folder where voices are stored + self.available_models = {} + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + required_files_multi_speaker = ["config.json", "model_file.pth", "speaker_ids.json"] + required_files_single_speaker = ["config.json", "model_file.pth"] + required_files_multi_language = ["config.json", "model_file.pth.tar", "speaker_ids.json", "language_ids.json"] + for subfolder in models_folder.iterdir(): + if subfolder.is_dir(): + model_name = subfolder.name + if all(subfolder.joinpath(file).exists() for file in required_files_multi_speaker): + self.available_models[f"vits - {model_name}"] = "vits_multi_speaker" + elif all(subfolder.joinpath(file).exists() for file in required_files_single_speaker): + self.available_models[f"vits - {model_name}"] = "vits_single_speaker" + elif all(subfolder.joinpath(file).exists() for file in required_files_multi_language): + self.available_models[f"vits - {model_name}"] = "vits_multi_language" + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + else: + self.available_models = {'No Models Available': 'vits'} # Change the name in here to your TTS engine name + print(f"[{self.branding}ENG] \033[91mWarning\033[0m: Model folder '{model_name}' is missing required") + print(f"[{self.branding}ENG] \033[91mWarning\033[0m: files or the folder does not exist.") + return self.available_models + + ############################################################# + ############################################################# + # CHANGE ME # POPULATE FILES LIST FROM VOICES DIRECTORY #### + ############################################################# + ############################################################# + # This function looks and reports back the list of possible voice your TTS engine can + # load in. Some TTS engines the voices are wav file samples (XTTS), some are models + # (Piper) and some are text (Parler) thats stored in a JSON file. We just need to + # populate the "voices" variable somehow and if no voices are found, we return + # "No Voices Found" back to the interface/api. + def voices_file_list(self): + try: + voices = [] + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + model_name = self.selected_model.replace("vits - ", "") + print("model_name", model_name) if self.debug_tts else None + model_type = self.available_models[self.selected_model] + print("model_type", model_type) if self.debug_tts else None + + if model_type == "vits_multi_speaker" or model_type == "vits_multi_language": + speaker_ids_path = self.main_dir / "models" / "vits" / model_name / "speaker_ids.json" + if speaker_ids_path.exists(): + with open(speaker_ids_path, "r") as f: + speaker_ids = json.load(f) + voices = [speaker for speaker in speaker_ids.keys() if speaker != "ED\n"] + else: + print(f"[{self.branding}ENG] \033[91mWarning\033[0m: speaker_ids.json not found for model \033[0m'{model_name}'\033[0m.") if self.debug_tts else None + + elif model_type == "vits_single_speaker": + voices = ["default"] # Return a list containing only the default voice for single-speaker models + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + if not voices: # If the voices list is empty + return ["No Voices Found"] # Return a list with 'No Voices Found' if there are no voices/voice models + return voices # Return the list of voices + except Exception as e: + print(f"[{self.branding}ENG] \033[91mError\033[0m: Voices/Voice Models not found. Cannot load a list of voices.") + print(f"[{self.branding}ENG] Exception: {str(e)}") # Optional: Print the exception message for debugging + return ["No Voices Found"] + + ############################# + ############################# + # CHANGE ME # Model loading # + ############################# + ############################# + # This function will handle the loading of your model, into VRAM/CUDA, System RAM or whatever. + # In XTTS, which has 2x model loader types, there are 2x loaders. They are called by "def handle_tts_method_change" + # In Piper we fake a model loader as Piper doesnt actually load a model into CUDA/System RAM as such. So, in that + # situation, api_manual_load_model is kind of a blank function. Though we do set self.is_tts_model_loaded = True + # as this is used elsewhere in the scripts to confirm that a model is available to be used for TTS generation. + # We always check for "No Models Available" being sent as that means we are trying to load in a model that + # doesnt exist/wasnt found on script start-up e.g. someone deleted the model from the folder or something. + async def api_manual_load_model(self, model_name): + if "No Models Available" in self.available_models: + print(f"[{self.branding}ENG] \033[91mError\033[0m: No models for this TTS engine were found to load. Please download a model.") + return + model_path = self.main_dir / "models" / "vits" / model_name # change the name to the correct folder name + print("model_path is:", model_path) if self.debug_tts_variables else None + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + # Load the config file + config_file_path = model_path / "config.json" + with open(config_file_path, "r") as f: + config_dict = json.load(f) + # Check if the model is single-speaker, multi-speaker, or multi-language + model_type = self.available_models[f"vits - {model_name}"] + use_speaker_embedding = config_dict["model_args"]["use_speaker_embedding"] + num_speakers = config_dict["model_args"]["num_speakers"] + if model_type == "vits_multi_speaker" or model_type == "vits_multi_language": + # Update the speakers_file path in the config dictionary + config_dict["model_args"]["speakers_file"] = str(model_path / "speaker_ids.json") + speakers_file = model_path / "speaker_ids.json" + else: + # Set speakers_file to None for single-speaker models + config_dict["model_args"]["speakers_file"] = None + speakers_file = None + # Save the updated config dictionary back to the config.json file + with open(config_file_path, "w") as f: + json.dump(config_dict, f, indent=4) + if model_type == "vits_multi_language": + # Load multi-language model + self.model = Synthesizer( + tts_checkpoint=model_path / "model_file.pth.tar", + tts_config_path=config_file_path, + tts_speakers_file=speakers_file, + tts_languages_file=model_path / "language_ids.json", + use_cuda=self.cuda_is_available, + ) + elif not use_speaker_embedding and num_speakers == 0: + # Single-speaker model without speaker embeddings + self.model = Synthesizer( + tts_checkpoint=model_path / "model_file.pth", + tts_config_path=config_file_path, + use_cuda=self.cuda_is_available, + ) + else: + # Multi-speaker model or single-speaker model with speaker embeddings + self.model = Synthesizer( + tts_checkpoint=model_path / "model_file.pth", + tts_config_path=config_file_path, + tts_speakers_file=speakers_file, + use_cuda=self.cuda_is_available, + ) + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + self.is_tts_model_loaded = True + return self.model + + ############################### + ############################### + # CHANGE ME # Model unloading # + ############################### + ############################### + # This function will handle the UN-loading of your model, from VRAM/CUDA, System RAM or whatever. + # In XTTS, that model loads into CUDA/System Ram, so when we swap models, we want to unload the current model + # free up the memory and then load in the new model to VRAM/CUDA. On the flip side of that, Piper doesnt + # doesnt load into memory, so we just need to put a fake function here that doesnt really do anything + # other than set "self.is_tts_model_loaded = False", which would be set back to true by the model loader. + # So look at the Piper model_engine.py if you DONT need to unload models. + async def unload_model(self): + self.is_tts_model_loaded = False + if not self.current_model_loaded == None: + print(f"[{self.branding}ENG] \033[94mUnloading model \033[0m") if self.debug_tts else None + if hasattr(self, 'model'): + del self.model + if torch.cuda.is_available(): + torch.cuda.empty_cache() + return None + + ################################################################################################################################### + ################################################################################################################################### + # CHANGE ME # Model changing. Unload out old model and load in a new one # XTTS is very unusal as it has 2x model loading methods # + ################################################################################################################################### + ################################################################################################################################### + # This function is your central model loading/unloading handler that deals with the above functions as necesary, to call loading, unloading, + # swappng DeepSpeed, Low vram etc. This function gets called with a "engine name - model name" type call. In XTTS, because there are 2x + # model loader types, (XTTS and APILocal), we take tts_method and split the "engine name - model name" into a loader type and the model + # that it needs to load in and then we call the correct loader function. Whereas in Piper, which doesnt load models into memory at all, + # we just have a fake function that doesnt really do anything. We always check to see if the model name has "No Models Available" in the + # name thats sent over, just to catch any potential errors. We display the start load time and end load time. Thats about it. + async def handle_tts_method_change(self, tts_method): + generate_start_time = time.time() # Record the start time of loading the model + if "No Models Available" in self.available_models: + print(f"[{self.branding}ENG] \033[91mError\033[0m: No models for this TTS engine were found to load. Please download a model.") + return False + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + await self.unload_model() + # Set other parameters to False + if tts_method.startswith("vits"): + model_name = tts_method.split(" - ")[1] + print(f"[{self.branding}ENG]\033[94m Model/Engine :\033[93m {model_name}\033[94m loading into\033[93m", self.device,"\033[0m") + self.model = await self.api_manual_load_model(model_name) + self.current_model_loaded = f"vits - {model_name}" + self.selected_model = tts_method # Update self.selected_model + else: + self.current_model_loaded = None + return False + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + generate_end_time = time.time() # Create an end timer for calculating load times + generate_elapsed_time = generate_end_time - generate_start_time # Calculate start time minus end time + print(f"[{self.branding}ENG] \033[94mLoad time :\033[93m {generate_elapsed_time:.2f} seconds.\033[0m") # Print out the result of the load time + return True + + ########################################################################################################################################## + ########################################################################################################################################## + # CHANGE ME # Model changing. Unload out old model and load in a new one # XTTS is very unusal as it has 2x model TTS generation methods # + ########################################################################################################################################## + ########################################################################################################################################## + # In here all the possible options are sent over (text, voice to use, lanugage, speed etc etc) and its up to you how you use them, or not. + # obviously if your TTS engine doesnt support speed for example, generationspeed_capable should be set False in your model_settings.JSON file + # and a fake "generationspeed_set" value should be set. This allows a fake value to be sent over from the main script, even though it + # wouldnt actually ever be used in the generation below. Nonethless all these values, real or just whats inside the configuration file + # will be sent over for use. + # Setting the xxxxxxx_capabale in the model_settings.JSON file, will enable/disable them being selectable by the user. For example, if you + # set "generationspeed_capable" as false in the model_settings.JSON file, a user will not be able to select OR set the setting for + # generation speed. + # One thing to note is that we HAVE to have something in this generation request that is synchronous from the way its called, which means + # we have to have an option for Streaming, even if our TTS engine doesnt support streaming. So in that case, we would set streaming_capable + # as false in our model_settings.JSON file, meaning streaming will never be called. However, we have to put a fake streaming routine in our + # function below (or a real function if it does support streaming of course). Parler has an example of a fake streaming function, which is + # very clearly highlighted in its model_engine.py script. + # Piper TTS, which uses command line based calls and therefore has different ones for Windows and Linux/Mac, has an example of doing this + # within its model_engine.py file. + async def generate_tts(self, text, voice, language, temperature, repetition_penalty, speed, pitch, output_file, streaming): + print(f"[{self.branding}Debug] Entered model_engine.py generate_tts function") if self.debug_tts else None + if not self.is_tts_model_loaded: # Check if a model is loaded and error out if not. + error_message = f"[{self.branding}ENG] \033[91mError\033[0m: You currently have no TTS model loaded." + print(error_message) + raise HTTPException(status_code=400, detail="You currently have no TTS model loaded.") # Raise an exception with a meaningful HTTP status code + self.tts_generating_lock = True # Set the tts_generating lock to True, which stops other generation requests being sent into the pipeline + print(f"[{self.branding}Debug] Checking low VRAM") if self.debug_tts else None + if self.lowvram_enabled and self.device == "cpu": # If necessary, move the model out of System Ram to VRAM + print(f"[{self.branding}Debug] Switching device") if self.debug_tts else None + await self.handle_lowvram_change() + print(f"[{self.branding}Debug] Setting a generate time") if self.debug_tts else None + generate_start_time = time.time() # Record the start time of generating TTS + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + if self.current_model_loaded.startswith("vits"): + print(f"[{self.branding}Debug] generate_tts Text arriving at engine {text}") if self.debug_tts else None + model_type = self.available_models[self.current_model_loaded] + if model_type == "vits_multi_speaker": + # Get the available speakers for the current model + model_name = self.current_model_loaded.replace("vits - ", "") + speaker_ids_path = self.main_dir / "models" / "vits" / model_name / "speaker_ids.json" + if speaker_ids_path.exists(): + with open(speaker_ids_path, "r") as f: + speaker_ids = json.load(f) + available_speakers = [speaker for speaker in speaker_ids.keys() if speaker != "ED\n"] + else: + print(f"[{self.branding}ENG] \033[91mWarning\033[0m: speaker_ids.json not found for model '{model_name}'.") + available_speakers = [] + + # Check if the specified voice matches any of the available speakers + if voice in available_speakers: + speaker_idx = voice + else: + # Use the first available speaker as the default + if available_speakers: + speaker_idx = available_speakers[0] + print(f"[{self.branding}ENG] Incorrect or no voice specified, using default voice: {speaker_idx}") + else: + speaker_idx = None + print(f"[{self.branding}ENG] No valid speakers found for the current model.") + + print(f"[{self.branding}Debug] generate_tts speaker_idx is:", speaker_idx) if self.debug_tts else None + print(f"[{self.branding}Debug] generate_tts output_file is:", output_file) if self.debug_tts else None + + # Generate TTS with speaker_name argument + if speaker_idx is not None: + wavs = self.model.tts(text, speaker_name=speaker_idx) + else: + wavs = None + else: + # Generate TTS without speaker_name argument for single-speaker models + wavs = self.model.tts(text) + + streaming = False + if streaming and wavs is not None: + # Streaming-specific operations + for wav_chunk in wavs: + yield wav_chunk.numpy().tobytes() + else: + if wavs is not None: + # Save the generated audio to a file + self.model.save_wav(wavs, output_file) + yield b"" # Yield an empty byte string to indicate completion + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + generate_end_time = time.time() # Record the end time to generate TTS + generate_elapsed_time = generate_end_time - generate_start_time + print(f"[{self.branding}GEN] \033[94mTTS Generate: \033[93m{generate_elapsed_time:.2f} seconds. \033[94mLowVRAM: \033[33m{self.lowvram_enabled} \033[94mDeepSpeed: \033[33m{self.deepspeed_enabled}\033[0m") + if self.lowvram_enabled and self.device == "cuda" and self.tts_narrator_generatingtts == False: + await self.handle_lowvram_change() + self.tts_generating_lock = False # Unlock the TTS generation queue to allow TTS generation requests to come in again. + print(f"[{self.branding}Debug] generate_tts function completed") if self.debug_tts else None + return + + diff --git a/system/tts_engines/vits/model_settings.json b/system/tts_engines/vits/model_settings.json new file mode 100644 index 0000000..24899c5 --- /dev/null +++ b/system/tts_engines/vits/model_settings.json @@ -0,0 +1,42 @@ +{ + "model_details": { + "manufacturer_name": "Coqui", + "manufacturer_website": "https://github.com/coqui-ai/TTS and also updated engine https://github.com/idiap/coqui-ai-TTS/", + "model_description": "VITS models come in single and multi-speaker variants. VITS (Conditional Variational Autoencoder with Adversarial Learning for End-to-End Text-to-Speech ) is an End-to-End (encoder -> vocoder together) TTS model that takes advantage of SOTA DL techniques like GANs, VAE, Normalizing Flows. It does not require external alignment annotations and learns the text-to-audio alignment using MAS, as explained in the paper. The model architecture is a combination of GlowTTS encoder and HiFiGAN vocoder. It is a feed-forward model with x67.12 real-time factor on a GPU. \n\ntts_models--en--vctk--vits is a good example of a multi speaker VITS model." + }, + "model_capabilties": { + "audio_format": "wav", + "deepspeed_capable": false, + "generationspeed_capable": false, + "languages_capable": false, + "lowvram_capable": true, + "multimodel_capable": true, + "repetitionpenalty_capable": false, + "streaming_capable": false, + "temperature_capable": false, + "multivoice_capable": true, + "pitch_capable": false, + "windows_capable": true, + "linux_capable": true, + "mac_capable": true + }, + "settings": { + "def_character_voice": "p225", + "def_narrator_voice": "p226", + "deepspeed_enabled": false, + "engine_installed": true, + "generationspeed_set": 1, + "lowvram_enabled": true, + "pitch_set": 0, + "repetitionpenalty_set": 10, + "temperature_set": 0.75 + }, + "openai_voices": { + "alloy": "p225", + "echo": "p225", + "fable": "p225", + "nova": "p225", + "onyx": "p225", + "shimmer": "p225" + } +} \ No newline at end of file diff --git a/system/tts_engines/vits/vits_settings_page.py b/system/tts_engines/vits/vits_settings_page.py new file mode 100644 index 0000000..07f6d8e --- /dev/null +++ b/system/tts_engines/vits/vits_settings_page.py @@ -0,0 +1,395 @@ +import os +import json +import shutil +import zipfile +import requests +import gradio as gr +from tqdm import tqdm +from pathlib import Path +this_dir = Path(__file__).parent.resolve() # Sets up self.this_dir as a variable for the folder THIS script is running in. +main_dir = Path(__file__).parent.parent.parent.parent.resolve() # Sets up self.main_dir as a variable for the folder AllTalk is running in + +########################################################################## +# REQUIRED CHANGE # +# Populate the voices list, using the method specific to your TTS engine # +########################################################################## +# This function is responsible for populating the list of available voices for your TTS engine. +# You need to modify this function to use the appropriate method for your engine to retrieve the voice list. +# +# The current implementation lists all the WAV files in a "voices" directory, which may not be suitable for your engine. +# You should replace the `xxxx_voices_file_list` function name to match your engine name. For example, if your engine +# is named "mytts", the function should be named `mytts_voices_file_list`. +# +# You will also neef to update the code with your own implementation that retrieves the voice list according to your +# engine's specific requirements. Typically this is the same code as will be in your model_engine.py file. +# +# For example, if your engine has a dedicated API or configuration file for managing voices, you should modify this +# function to interact with that API or read from that configuration file. +# +# After making the necessary changes, this function should return a list of available voices that can be used +# in your TTS engine's settings page. +def vits_voices_file_list(): + models_folder = main_dir / "models" / "vits" + # Create the folder if it doesn't exist + if not models_folder.exists(): + models_folder.mkdir(parents=True, exist_ok=True) + voices = [] + available_models = {} + required_files_multi_speaker = ["config.json", "model_file.pth", "speaker_ids.json"] + required_files_single_speaker = ["config.json", "model_file.pth"] + required_files_multi_language = ["config.json", "model_file.pth.tar", "speaker_ids.json", "language_ids.json"] + for subfolder in models_folder.iterdir(): + if subfolder.is_dir(): + model_name = subfolder.name + if all(subfolder.joinpath(file).exists() for file in required_files_multi_speaker): + available_models[f"VITS - {model_name}"] = "VITS_multi_speaker" + elif all(subfolder.joinpath(file).exists() for file in required_files_single_speaker): + available_models[f"VITS - {model_name}"] = "VITS_single_speaker" + elif all(subfolder.joinpath(file).exists() for file in required_files_multi_language): + available_models[f"VITS - {model_name}"] = "VITS_multi_language" + else: + print(f"[AllTalk ENG] \033[91mWarning\033[0m: Model '{model_name}' does not match any known model type.") + model_type = available_models.get(f"VITS - {model_name}") + if model_type == "VITS_multi_speaker" or model_type == "VITS_multi_language": + speaker_ids_path = subfolder / "speaker_ids.json" + if speaker_ids_path.exists(): + with open(speaker_ids_path, "r") as f: + speaker_ids = json.load(f) + speakers = [speaker for speaker in speaker_ids.keys() if speaker != "ED\n"] + voices.extend(speakers) + else: + print(f"[AllTalk ENG] \033[91mWarning\033[0m: speaker_ids.json not found for model '{model_name}'.") + elif model_type == "VITS_single_speaker": + voices.append("default") + # Remove duplicate speaker names + voices = list(set(voices)) + # Sort the voices alphabetically + voices.sort() + # Move the "default" voice to the top of the list + if "default" in voices: + voices.remove("default") + voices.insert(0, "default") + return voices + +###################################################### +# REQUIRED CHANGE # +# Imports and saves the TTS engine-specific settings # +###################################################### +# This function is responsible for importing and saving the settings specific to your TTS engine. +# You need to make the following change: +# +# 1. Change the name of the function `xxxx_model_update_settings` to match your engine's name. +# For example, if your engine is named "mytts", the function should be named `mytts_model_update_settings`. +# +# After making this change, the function will load the model settings from a JSON file, update the settings and voice +# dictionaries with the values provided as arguments, and save the updated settings back to the JSON file. +# +# You do not need to modify the function's logic or any other part of the code. +def vits_model_update_settings(def_character_voice_gr, def_narrator_voice_gr, lowvram_enabled_gr, deepspeed_enabled_gr, temperature_set_gr, repetitionpenalty_set_gr, pitch_set_gr, generationspeed_set_gr, alloy_gr, echo_gr, fable_gr, nova_gr, onyx_gr, shimmer_gr): + # Load the model_config_data from the JSON file + with open(os.path.join(this_dir, "model_settings.json"), "r") as f: + model_config_data = json.load(f) + # Update the settings and openai_voices dictionaries with the new values + model_config_data["settings"]["def_character_voice"] = def_character_voice_gr + model_config_data["settings"]["def_narrator_voice"] = def_narrator_voice_gr + model_config_data["openai_voices"]["alloy"] = alloy_gr + model_config_data["openai_voices"]["echo"] = echo_gr + model_config_data["openai_voices"]["fable"] = fable_gr + model_config_data["openai_voices"]["nova"] = nova_gr + model_config_data["openai_voices"]["onyx"] = onyx_gr + model_config_data["openai_voices"]["shimmer"] = shimmer_gr + model_config_data["settings"]["lowvram_enabled"] = lowvram_enabled_gr == "Enabled" + model_config_data["settings"]["deepspeed_enabled"] = deepspeed_enabled_gr == "Enabled" + model_config_data["settings"]["temperature_set"] = temperature_set_gr + model_config_data["settings"]["repetitionpenalty_set"] = repetitionpenalty_set_gr + model_config_data["settings"]["pitch_set"] = pitch_set_gr + model_config_data["settings"]["generationspeed_set"] = generationspeed_set_gr + # Save the updated model_config_data to the JSON file + with open(os.path.join(this_dir, "model_settings.json"), "w") as f: + json.dump(model_config_data, f, indent=4) + return "Settings updated successfully!" + +####################################################### +# REQUIRED CHANGE # +# Sets up the engine-specific settings page in Gradio # +####################################################### +# This function sets up the Gradio interface for the settings page specific to your TTS engine. +# You need to make the following changes: +# +# 1. Change the name of the function `xxxx_model_alltalk_settings` to match your engine's name. +# For example, if your engine is named "mytts", the function should be named `mytts_model_alltalk_settings`. +# +# 2. Change the name of the `submit_button.click` function call to match the name you gave to the function +# that imports and saves your engine's settings (the function you modified above). +# +# 3. Change the name of the `voice_list` function call to match the name of the function that lists +# the available voices for your TTS engine. +# +# 4. Change the 'title' of the `gr.Blocks` to match your engine's name e.g. title="mytts TTS" +# +# After making these changes, this function will create and return the Gradio interface for your TTS engine's +# settings page, allowing users to configure various options and voice selections. +def vits_model_alltalk_settings(model_config_data): + features_list = model_config_data['model_capabilties'] + voice_list = vits_voices_file_list() + with gr.Blocks(title="vits TTS") as app: + with gr.Tab("Default Settings"): + with gr.Row(): + lowvram_enabled_gr = gr.Radio(choices={"Enabled": "true", "Disabled": "false"}, label="Low VRAM" if model_config_data["model_capabilties"]["lowvram_capable"] else "Low VRAM N/A", value="Enabled" if model_config_data["settings"]["lowvram_enabled"] else "Disabled", interactive=model_config_data["model_capabilties"]["lowvram_capable"]) + deepspeed_enabled_gr = gr.Radio(choices={"Enabled": "true", "Disabled": "false"}, label="DeepSpeed Activate" if model_config_data["model_capabilties"]["deepspeed_capable"] else "DeepSpeed N/A", value="Enabled" if model_config_data["settings"]["deepspeed_enabled"] else "Disabled", interactive=model_config_data["model_capabilties"]["deepspeed_capable"]) + temperature_set_gr = gr.Slider(value=float(model_config_data["settings"]["temperature_set"]), minimum=0, maximum=1, step=0.05, label="Temperature" if model_config_data["model_capabilties"]["temperature_capable"] else "Temperature N/A", interactive=model_config_data["model_capabilties"]["temperature_capable"]) + repetitionpenalty_set_gr = gr.Slider(value=float(model_config_data["settings"]["repetitionpenalty_set"]), minimum=1, maximum=20, step=1, label="Repetition Penalty" if model_config_data["model_capabilties"]["repetitionpenalty_capable"] else "Repetition N/A", interactive=model_config_data["model_capabilties"]["repetitionpenalty_capable"]) + pitch_set_gr = gr.Slider(value=float(model_config_data["settings"]["pitch_set"]), minimum=-10, maximum=10, step=1, label="Pitch" if model_config_data["model_capabilties"]["pitch_capable"] else "Pitch N/A", interactive=model_config_data["model_capabilties"]["pitch_capable"]) + generationspeed_set_gr = gr.Slider(value=float(model_config_data["settings"]["generationspeed_set"]), minimum=0.25, maximum=2.00, step=0.25, label="Speed" if model_config_data["model_capabilties"]["generationspeed_capable"] else "Speed N/A", interactive=model_config_data["model_capabilties"]["generationspeed_capable"]) + with gr.Row(): + with gr.Column(): + gr.Markdown("### OpenAI Voice Mappings") + with gr.Group(): + with gr.Row(): + alloy_gr = gr.Dropdown(value=model_config_data["openai_voices"]["alloy"], label="Alloy", choices=voice_list, allow_custom_value=True) + echo_gr = gr.Dropdown(value=model_config_data["openai_voices"]["echo"], label="Echo", choices=voice_list, allow_custom_value=True) + with gr.Row(): + fable_gr = gr.Dropdown(value=model_config_data["openai_voices"]["fable"], label="Fable", choices=voice_list, allow_custom_value=True) + nova_gr = gr.Dropdown(value=model_config_data["openai_voices"]["nova"], label="Nova", choices=voice_list, allow_custom_value=True) + with gr.Row(): + onyx_gr = gr.Dropdown(value=model_config_data["openai_voices"]["onyx"], label="Onyx", choices=voice_list, allow_custom_value=True) + shimmer_gr = gr.Dropdown(value=model_config_data["openai_voices"]["shimmer"], label="Shimmer", choices=voice_list, allow_custom_value=True) + with gr.Column(): + gr.Markdown("### Default Voices") + with gr.Row(): + def_character_voice_gr = gr.Dropdown(value=model_config_data["settings"]["def_character_voice"], label="Default/Character Voice", choices=voice_list, allow_custom_value=True) + def_narrator_voice_gr = gr.Dropdown(value=model_config_data["settings"]["def_narrator_voice"], label="Narrator Voice", choices=voice_list, allow_custom_value=True) + with gr.Group(): + with gr.Row(): + details_text = gr.Textbox(label="Details", show_label=False, lines=5, interactive=False, value="In this section, you can set the default settings for this TTS engine. Settings that are not supported by the current engine will be greyed out and cannot be selected. Default voices specified here will be used when no specific voice is provided in the TTS generation request. If a voice is specified in the request, it will override these default settings. When using the OpenAI API compatable API with this TTS engine, the voice mappings will be applied. As the OpenAI API has a limited set of 6 voices, these mappings ensure compatibility by mapping the OpenAI voices to the available voices in this TTS engine.") + with gr.Row(): + submit_button = gr.Button("Update Settings") + output_message = gr.Textbox(label="Output Message", interactive=False, show_label=False) + submit_button.click(vits_model_update_settings, inputs=[def_character_voice_gr, def_narrator_voice_gr, lowvram_enabled_gr, deepspeed_enabled_gr, temperature_set_gr, repetitionpenalty_set_gr, pitch_set_gr, generationspeed_set_gr, alloy_gr, echo_gr, fable_gr, nova_gr, onyx_gr, shimmer_gr], outputs=output_message) + + ########################################################################################### + # Do not change this section apart from "TTS Engine Name" value to match your engine name # + ########################################################################################### + with gr.Tab("Engine Information"): + with gr.Row(): + with gr.Group(): + gr.Textbox(label="Manufacturer Name", value=model_config_data['model_details']['manufacturer_name'], interactive=False) + gr.Textbox(label="Manufacturer Website/TTS Engine Support", value=model_config_data['model_details']['manufacturer_website'], interactive=False) + gr.Textbox(label="Engine/Model Description", value=model_config_data['model_details']['model_description'], interactive=False, lines=13) + with gr.Column(): + with gr.Row(): + gr.Textbox(label="DeepSpeed Capable", value='Yes' if features_list['deepspeed_capable'] else 'No', interactive=False) + gr.Textbox(label="Pitch Capable", value='Yes' if features_list['pitch_capable'] else 'No', interactive=False) + gr.Textbox(label="Generation Speed Capable", value='Yes' if features_list['generationspeed_capable'] else 'No', interactive=False) + with gr.Row(): + gr.Textbox(label="Repetition Penalty Capable", value='Yes' if features_list['repetitionpenalty_capable'] else 'No', interactive=False) + gr.Textbox(label="Multi Languages Capable", value='Yes' if features_list['languages_capable'] else 'No', interactive=False) + gr.Textbox(label="Streaming Capable", value='Yes' if features_list['streaming_capable'] else 'No', interactive=False) + with gr.Row(): + gr.Textbox(label="Low VRAM Capable", value='Yes' if features_list['lowvram_capable'] else 'No', interactive=False) + gr.Textbox(label="Temperature Capable", value='Yes' if features_list['temperature_capable'] else 'No', interactive=False) + gr.Textbox(label="Multi Model Capable Engine", value='Yes' if features_list['multimodel_capable'] else 'No', interactive=False) + with gr.Row(): + gr.Textbox(label="Multi Voice Capable Models", value='Yes' if features_list['multivoice_capable'] else 'No', interactive=False) + gr.Textbox(label="Default Audio output format", value=model_config_data['model_capabilties']['audio_format'], interactive=False) + gr.Textbox(label="TTS Engine Name", value="VITS", interactive=False) + with gr.Row(): + gr.Textbox(label="Windows Support", value='Yes' if features_list['windows_capable'] else 'No', interactive=False) + gr.Textbox(label="Linux Support", value='Yes' if features_list['linux_capable'] else 'No', interactive=False) + gr.Textbox(label="Mac Support", value='Yes' if features_list['mac_capable'] else 'No', interactive=False) + with gr.Row(): + gr.Markdown(""" + #### 🟧 DeepSpeed Capable + DeepSpeed is a deep learning optimization library that can significantly speed up model training and inference. If a model is DeepSpeed capable, it means it can utilize DeepSpeed to accelerate the generation of text-to-speech output. This requires the model to be loaded into CUDA/VRAM on an Nvidia GPU and the model's inference method to support DeepSpeed. + #### 🟧 Pitch Capable + Pitch refers to the highness or lowness of a sound. If a model is pitch capable, it can adjust the pitch of the generated speech, allowing for more expressive and varied output. + #### 🟧 Generation Speed Capable + Generation speed refers to the rate at which the model can generate text-to-speech output. If a model is generation speed capable, it means the speed of the generated speech can be adjusted, making it faster or slower depending on the desired output. + #### 🟧 Repetition Penalty Capable + Repetition penalty is a technique used to discourage the model from repeating the same words or phrases multiple times in the same sounding way. If a model is repetition penalty capable, it can apply this penalty during generation to improve the diversity and naturalness of the output. + #### 🟧 Multi-Languages Capable + Multi-language capable models can generate speech in multiple languages. This means that the model has been trained on data from different languages and can switch between them during generation. Some models are language-specific. + #### 🟧 Multi-Voice Capable + Multi-voice capable models generate speech in multiple voices or speaking styles. This means that the model has been trained on data from different speakers and can mimic their voices during generation, or is a voice cloning model that can generate speech based on the input sample. + """) + gr.Markdown(""" + #### 🟧 Streaming Capable + Streaming refers to the ability to generate speech output in real-time, without the need to generate the entire output before playback. If a model is streaming capable, it can generate speech on-the-fly, allowing for faster response times and more interactive applications. + #### 🟧 Low VRAM Capable + VRAM (Video Random Access Memory) is a type of memory used by GPUs to store and process data. If a model is low VRAM capable, it can efficiently utilize the available VRAM by moving data between CPU and GPU memory as needed, allowing for generation even on systems with limited VRAM where it may be competing with an LLM model for VRAM. + #### 🟧 Temperature Capable + Temperature is a hyperparameter that controls the randomness of the generated output. If a model is temperature capable, the temperature can be adjusted to make the output more or less random, affecting the creativity and variability of the generated speech. + #### 🟧 Multi-Model Capable Engine + If an engine is multi-model capable, it means that it can support and utilize multiple models for text-to-speech generation. This allows for greater flexibility and the ability to switch between different models depending on the desired output. Different models may be capable of different languages, specific languages, voices, etc. + #### 🟧 Default Audio Output Format + Specifies the file format in which the generated speech will be saved. Common audio formats include WAV, MP3, FLAC, Opus, AAC, and PCM. If you want different outputs, you can set the transcode function to change the output audio, though transcoding will add a little time to the generation and is not available for streaming generation. + #### 🟧 Windows/Linux/Mac Support + These indicators show whether the model and engine are compatible with Windows, Linux, or macOS. However, additional setup or requirements may be necessary to ensure full functionality on your operating system. Please note that full platform support has not been extensively tested. + """) + + ####################################################################################################################################################################################################### + # REQUIRED CHANGE # + # You will need to build a custom method to identify if your models are installed and download them. Store your models list in the available_models.json file, alongside your model_settings.json etc # + ####################################################################################################################################################################################################### + with gr.Tab("Models/Voices Download"): + with gr.Row(): + # Load the available models from the JSON file + with open(os.path.join(this_dir, "available_models.json"), "r") as f: + available_models = json.load(f) + # Extract the model names for the dropdown + model_names = [model["model_name"] for model in available_models["models"]] + # Create the dropdown + model_dropdown = gr.Dropdown(choices=sorted(model_names), label="Select Model", value=model_names[0]) + download_button = gr.Button("Download Model/Missing Files") + + with gr.Row(): + download_status = gr.Textbox(label="Download Status") + + def download_model(model_name): + # Find the selected model in the available models + selected_model = next(model for model in available_models["models"] if model["model_name"] == model_name) + # Get the folder path and ZIP file URL + vits_folder = os.path.join(main_dir, "models", "vits") + folder_path = os.path.join(vits_folder, selected_model["folder_path"]) + zip_url = selected_model["github_rls_url"] + # Check if the model is already downloaded based on the file structure + required_files = ["config.json"] + if os.path.exists(os.path.join(folder_path, "model_file.pth.tar")): + required_files.append("model_file.pth.tar") + else: + required_files.append("model_file.pth") + if os.path.exists(os.path.join(folder_path, "speaker_ids.json")): + required_files.append("speaker_ids.json") + if os.path.exists(os.path.join(folder_path, "language_ids.json")): + required_files.append("language_ids.json") + if all(os.path.exists(os.path.join(folder_path, file)) for file in required_files): + return "The model is already downloaded. No need to download again." + else: + # Download the ZIP file to the "vits" folder + print(f"Downloading {model_name}...") + response = requests.get(zip_url, stream=True) + total_size_in_bytes = int(response.headers.get("content-length", 0)) + block_size = 1024 # 1 Kibibyte + progress_bar = tqdm(total=total_size_in_bytes, unit="iB", unit_scale=True) + zip_file_path = os.path.join(vits_folder, f"{model_name}.zip") + with open(zip_file_path, "wb") as file: + for data in response.iter_content(block_size): + progress_bar.update(len(data)) + file.write(data) + progress_bar.close() + # Extract the ZIP file contents to the desired folder + with zipfile.ZipFile(zip_file_path, "r") as zip_ref: + zip_ref.extractall(vits_folder) + # Remove the ZIP file + os.remove(zip_file_path) + # Delete the __MACOSX folder if it exists + macosx_folder = os.path.join(vits_folder, "__MACOSX") + if os.path.exists(macosx_folder): + shutil.rmtree(macosx_folder) + return "Model downloaded successfully!" + + def show_confirm_cancel(model_name): + selected_model = next(model for model in available_models["models"] if model["model_name"] == model_name) + folder_path = os.path.join(main_dir, "models", "vits", selected_model["folder_path"]) + required_files = ["config.json"] + if os.path.exists(os.path.join(folder_path, "model_file.pth.tar")): + required_files.append("model_file.pth.tar") + else: + required_files.append("model_file.pth") + if os.path.exists(os.path.join(folder_path, "speaker_ids.json")): + required_files.append("speaker_ids.json") + if os.path.exists(os.path.join(folder_path, "language_ids.json")): + required_files.append("language_ids.json") + if all(os.path.exists(os.path.join(folder_path, file)) for file in required_files): + return [gr.update(visible=True), gr.update(visible=True), gr.update(visible=True)] + else: + return [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)] + + def confirm_download(model_name): + # Find the selected model in the available models + selected_model = next(model for model in available_models["models"] if model["model_name"] == model_name) + # Get the folder path and ZIP file URL + vits_folder = os.path.join(main_dir, "models", "vits") + folder_path = os.path.join(vits_folder, selected_model["folder_path"]) + zip_url = selected_model["github_rls_url"] + # Download the ZIP file to the "vits" folder + print(f"Downloading {model_name}...") + response = requests.get(zip_url, stream=True) + total_size_in_bytes = int(response.headers.get("content-length", 0)) + block_size = 1024 # 1 Kibibyte + progress_bar = tqdm(total=total_size_in_bytes, unit="iB", unit_scale=True) + zip_file_path = os.path.join(vits_folder, f"{model_name}.zip") + with open(zip_file_path, "wb") as file: + for data in response.iter_content(block_size): + progress_bar.update(len(data)) + file.write(data) + progress_bar.close() + # Extract the ZIP file contents to the desired folder + with zipfile.ZipFile(zip_file_path, "r") as zip_ref: + zip_ref.extractall(vits_folder) + # Remove the ZIP file + os.remove(zip_file_path) + # Delete the __MACOSX folder if it exists + macosx_folder = os.path.join(vits_folder, "__MACOSX") + if os.path.exists(macosx_folder): + shutil.rmtree(macosx_folder) + return [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)] + + download_button.click(download_model, inputs=model_dropdown, outputs=download_status) + + with gr.Row(): + confirm_button = gr.Button("Download Anyway", visible=False) + cancel_button = gr.Button("Cancel", visible=False) + download_button.click(show_confirm_cancel, model_dropdown, [confirm_button, download_button, cancel_button]) + confirm_button.click(confirm_download, model_dropdown, [confirm_button, download_button, cancel_button]) + cancel_button.click(lambda: [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)], None, [confirm_button, download_button, cancel_button]) + + ################################################################################################### + # REQUIRED CHANGE # + # Add any engine specific help, bugs, issues, operating system specifc requirements/setup in here # + # Please use Markdown format, so gr.Markdown() with your markdown inside it. # + ################################################################################################### + with gr.Tab("Engine Help"): + with gr.Row(): + gr.Markdown(""" + ### 🟧 Where are the vits models stored? + This extension will download the models to `/alltalk_tts/models/vits/` folder.
+ + ### 🟧 Where are the voices stored for vits models? + Vits models themselves are the voices/have the voices built into them. Some models have mutliple voices embedded and some models only the one voice embedded in them.
+ + ### 🟧 Where are the outputs stored & Automatic output wav file deletion + Voice outputs are stored in `/alltalk_tts/outputs/`. You can configure automatic maintenance deletion of old wav files by setting `Del WAV's older than` in the global settings.
+ + > When `Disabled`, your output wav files will be left untouched.
+ > When set to a setting `1 Day` or greater, your output wav files older than that time period will be automatically deleted on start-up of AllTalk.
+ """) + gr.Markdown(""" + """) + + return app + +################################ +# REQUIRED CHANGE # +# Sets up the Gradio interface # +################################ +# This function sets up the Gradio interface for your TTS engine's settings page. +# You need to change the name of the function calls to match the names you set in the functions above. +# +# Specifically, you need to update the following: +# +# 1. The name of the function `xxxx_at_gradio_settings_page` to match your engine's name. +# For example, if your engine is named "mytts", the function should be named `mytts_at_gradio_settings_page`. +# +# 2. The name of the function call `xxxx_model_alltalk_settings(model_config_data)`. +# This should match the name you gave to the function that sets up the engine-specific settings page in Gradio. +# If you named that function `mytts_model_alltalk_settings`, then the call should be: +# `mytts_model_alltalk_settings(model_config_data)` +# +# After making these changes, this function will create and return the Gradio app for your TTS engine's settings page. +def vits_at_gradio_settings_page(model_config_data): + app = vits_model_alltalk_settings(model_config_data) + return app +def vits_at_gradio_settings_page(model_config_data): + app = vits_model_alltalk_settings(model_config_data) + return app \ No newline at end of file diff --git a/system/tts_engines/xtts/available_models.json b/system/tts_engines/xtts/available_models.json new file mode 100644 index 0000000..8f33b47 --- /dev/null +++ b/system/tts_engines/xtts/available_models.json @@ -0,0 +1,61 @@ +{ + "first_start_model": "xttsv2_2.0.3", + "models": [ + { + "model_name": "xttsv2_2.0.3", + "folder_path": "xttsv2_2.0.3", + "files_to_download": { + "LICENSE.txt": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/LICENSE.txt?download=true", + "README.md": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/README.md?download=true", + "config.json": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/config.json?download=true", + "model.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/model.pth?download=true", + "dvae.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/dvae.pth?download=true", + "mel_stats.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/mel_stats.pth?download=true", + "speakers_xtts.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/speakers_xtts.pth?download=true", + "vocab.json": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/vocab.json?download=true" + } + }, + { + "model_name": "xttsv2_2.0.2", + "folder_path": "xttsv2_2.0.2", + "files_to_download": { + "LICENSE.txt": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/LICENSE.txt?download=true", + "README.md": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/README.md?download=true", + "config.json": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/config.json?download=true", + "model.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/model.pth?download=true", + "dvae.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/dvae.pth?download=true", + "mel_stats.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/mel_stats.pth?download=true", + "speakers_xtts.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/speakers_xtts.pth?download=true", + "vocab.json": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/vocab.json?download=true" + } + }, + { + "model_name": "xttsv2_2.0.1", + "folder_path": "xttsv2_2.0.1", + "files_to_download": { + "LICENSE.txt": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.1/LICENSE.txt?download=true", + "README.md": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.1/README.md?download=true", + "config.json": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.1/config.json?download=true", + "model.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.1/model.pth?download=true", + "dvae.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.1/dvae.pth?download=true", + "mel_stats.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.1/mel_stats.pth?download=true", + "speakers_xtts.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.1/speakers_xtts.pth?download=true", + "vocab.json": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.1/vocab.json?download=true" + } + }, + { + "model_name": "xttsv2_2.0.0", + "folder_path": "xttsv2_2.0.0", + "files_to_download": { + "LICENSE.txt": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.0/LICENSE.txt?download=true", + "README.md": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.0/README.md?download=true", + "config.json": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.0/config.json?download=true", + "model.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.0/model.pth?download=true", + "dvae.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.0/dvae.pth?download=true", + "mel_stats.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.0/mel_stats.pth?download=true", + "speakers_xtts.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.0/speakers_xtts.pth?download=true", + "vocab.json": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.0/vocab.json?download=true" + } + } + ] + } \ No newline at end of file diff --git a/system/tts_engines/xtts/model_engine.py b/system/tts_engines/xtts/model_engine.py new file mode 100644 index 0000000..7c14395 --- /dev/null +++ b/system/tts_engines/xtts/model_engine.py @@ -0,0 +1,576 @@ +############################################### +# DONT CHANGE # These are base imports needed # +############################################### +import os +import sys +import json +import time +import torch +import logging +from pathlib import Path +from fastapi import (HTTPException) +logging.disable(logging.WARNING) +############################################### +# DONT CHANGE # Get Pytorch & Python versions # +############################################### +pytorch_version = torch.__version__ +cuda_version = torch.version.cuda +major, minor, micro = sys.version_info[:3] +python_version = f"{major}.{minor}.{micro}" +try: + import deepspeed + deepspeed_available = True +except ImportError: + deepspeed_available = False + pass + +############################################################################################################# +############################################################################################################# +# CHANGE ME # Run any specifc imports, requirements or setup any global vaiables needed for this TTS Engine # +############################################################################################################# +############################################################################################################# +# In this section you will import any imports that your specific TTS Engine will use. You will provide any +# start-up errors for those bits, as if you were starting up a normal Python script. Note the logging.disable +# a few lines up from here, you may want to # that out while debugging! +try: + import torchaudio + import wave + import io + import numpy as np + from TTS.tts.configs.xtts_config import XttsConfig + from TTS.tts.models.xtts import Xtts + from TTS.api import TTS + from TTS.utils.synthesizer import Synthesizer +except ModuleNotFoundError: + print( + f"[Startup] \033[91mWarning\033[0m Could not find the TTS module. Make sure to install the requirements for the alltalk_tts extension.", + f"[Startup] \033[91mWarning\033[0m Linux / Mac:\npip install -r extensions/alltalk_tts/requirements.txt\n", + f"[Startup] \033[91mWarning\033[0m Windows:\npip install -r extensions\\alltalk_tts\\requirements.txt\n", + f"[Startup] \033[91mWarning\033[0m If you used the one-click installer, paste the command above in the terminal window launched after running the cmd_ script. On Windows, that's cmd_windows.bat." + ) + raise + +############################################################# +# DONT CHANGE # Do not change the Class name from tts_class # +############################################################# +class tts_class: + def __init__(self): + ######################################################################## + # DONT CHANGE # Sets up the base variables required for any tts engine # + ######################################################################## + self.branding = None + self.this_dir = Path(__file__).parent.resolve() # Sets up self.this_dir as a variable for the folder THIS script is running in. + self.main_dir = Path(__file__).parent.parent.parent.parent.resolve() # Sets up self.main_dir as a variable for the folder AllTalk is running in + self.device = "cuda" if torch.cuda.is_available() else "cpu" # Sets up self.device to cuda if torch exists with Nvidia/CUDA, otherwise sets to cpu + self.cuda_is_available = torch.cuda.is_available() # Sets up cuda_is_available as a True/False to track if Nvidia/CUDA was found on the system + self.tts_generating_lock = False # Used to lock and unlock the tts generation process at the start/end of tts generation. + self.tts_stop_generation = False # Used in conjunction with tts_generating_lock to call for a stop to the current generation. If called (set True) it needs to be set back to False when generation has been stopped. + self.tts_narrator_generatingtts = False # Used to track if the current tts processes is narrator based. This can be used in conjunction with lowvram and device to avoid moving model between GPU(CUDA)<>RAM(CPU) each chunk of narrated text generated. + self.model = None # If loading a model into CUDA/VRAM/RAM "model" is used as the variable name to load and interact with (see the XTTS model_engine script for examples.) + self.is_tts_model_loaded = False # Used to track if a model is actually loaded in and error/fail things like TTS generation if its False + self.current_model_loaded = None # Stores the name of the currenly loaded in model + self.available_models = None # List of available models found by "def scan_models_folder" + self.setup_has_run = False # Tracks if async def setup(self) has run, by setting to True, so that the /api/ready endpoint can provide a "Ready" status + ############################################################################################## + # DONT CHANGE # Load in a list of the available TTS engines and the currently set TTS engine # + ############################################################################################## + tts_engines_file = os.path.join(self.main_dir, "system", "tts_engines", "tts_engines.json") + with open(tts_engines_file, "r") as f: + tts_engines_data = json.load(f) + self.engines_available = [engine["name"] for engine in tts_engines_data["engines_available"]] # A list of ALL the TTS engines available to be loaded by AllTalk + self.engine_loaded = tts_engines_data["engine_loaded"] # In "tts_engines.json" what is the currently set TTS engine loading into AllTalk + self.selected_model = tts_engines_data["selected_model"] # In "tts_engines.json" what is the currently set TTS model loading into AllTalk + ############################################################################ + # DONT CHANGE # Pull out all the settings for the currently set TTS engine # + ############################################################################ + with open(os.path.join(self.this_dir, "model_settings.json"), "r") as f: + tts_model_loaded = json.load(f) + # Access the model details + self.manufacturer_name = tts_model_loaded["model_details"]["manufacturer_name"] # The company/person/body that generated the TTS engine/models etc + self.manufacturer_website = tts_model_loaded["model_details"]["manufacturer_website"] # The website of the company/person/body where people can find more information + # Access the features the model is capable of: + self.audio_format = tts_model_loaded["model_capabilties"]["audio_format"] # This details the audio format your TTS engine is set to generate TTS in e.g. wav, mp3, flac, opus, acc, pcm. Please use only 1x format. + self.deepspeed_capable = tts_model_loaded["model_capabilties"]["deepspeed_capable"] # Is your model capable of DeepSpeed + self.deepspeed_available = 'deepspeed' in globals() # When we did the import earlier, at the top of this script, was DeepSpeed available for use + self.generationspeed_capable = tts_model_loaded["model_capabilties"]["generationspeed_capable"] # Does this TTS engine support changing the speed of the generated TTS + self.languages_capable = tts_model_loaded["model_capabilties"]["languages_capable"] # Are the actual models themselves capable of generating in multiple languages OR is each model language specific + self.lowvram_capable = tts_model_loaded["model_capabilties"]["lowvram_capable"] # Is this engine capable of using low VRAM (moving the model between CPU And GPU memory) + self.multimodel_capable = tts_model_loaded["model_capabilties"]["multimodel_capable"] # Is there just the one model or are there multiple models this engine supports. + self.repetitionpenalty_capable = tts_model_loaded["model_capabilties"]["repetitionpenalty_capable"] # Is this TTS engine capable of changing the repititon penalty + self.streaming_capable = tts_model_loaded["model_capabilties"]["streaming_capable"] # Is this TTS engine capabale of generating streaming audio + self.temperature_capable = tts_model_loaded["model_capabilties"]["temperature_capable"] # Is this TTS engine capable of changing the temperature of the models + self.multivoice_capable = tts_model_loaded["model_capabilties"]["multivoice_capable"] # Are the models multi-voice or single vocice models + self.pitch_capable = tts_model_loaded["model_capabilties"]["pitch_capable"] # Is this TTS engine capable of changing the pitch of the genrated TTS + # Access the current enginesettings + self.def_character_voice = tts_model_loaded["settings"]["def_character_voice"] # What is the current default main/character voice that will be used if no voice specified. + self.def_narrator_voice = tts_model_loaded["settings"]["def_narrator_voice"] # What is the current default narrator voice that will be used if no voice specified. + self.deepspeed_enabled = tts_model_loaded["settings"]["deepspeed_enabled"] # If its available, is DeepSpeed enabled for the TTS engine + self.engine_installed = tts_model_loaded["settings"]["engine_installed"] # Has the TTS engine been setup/installed (not curently used) + self.generationspeed_set = tts_model_loaded["settings"]["generationspeed_set"] # What is the set/stored speed for generation. + self.lowvram_enabled = tts_model_loaded["settings"]["lowvram_enabled"] # If its available, is LowVRAM enabled for the TTS engine + # Check if someone has enabled lowvram on a system that's not CUDA enabled + self.lowvram_enabled = False if not torch.cuda.is_available() else self.lowvram_enabled # If LowVRAM is mistakenly set and CUDA is not available, this will force it back off + self.repetitionpenalty_set = tts_model_loaded["settings"]["repetitionpenalty_set"] # What is the currenly set repitition policy of the model (If it support repetition) + self.temperature_set = tts_model_loaded["settings"]["temperature_set"] # What is the currenly set temperature of the model (If it support temp) + self.pitch_set = tts_model_loaded["settings"]["pitch_set"] # What is the currenly set pitch of the model (If it support temp) + # Gather the OpenAI API Voice Mappings + self.openai_alloy = tts_model_loaded["openai_voices"]["alloy"] # The TTS engine voice that will be mapped to Open AI Alloy voice + self.openai_echo = tts_model_loaded["openai_voices"]["echo"] # The TTS engine voice that will be mapped to Open AI Echo voice + self.openai_fable = tts_model_loaded["openai_voices"]["fable"] # The TTS engine voice that will be mapped to Open AI Fable voice + self.openai_nova = tts_model_loaded["openai_voices"]["nova"] # The TTS engine voice that will be mapped to Open AI Nova voice + self.openai_onyx = tts_model_loaded["openai_voices"]["onyx"] # The TTS engine voice that will be mapped to Open AI Onyx voice + self.openai_shimmer = tts_model_loaded["openai_voices"]["shimmer"] # The TTS engine voice that will be mapped to Open AI Shimmer voice + ################################################################### + # DONT CHANGE # Load params and api_defaults from confignew.json # + ################################################################### + # Define the path to the confignew.json file + configfile_path = self.main_dir / "confignew.json" + # Load config file and get settings + with open(configfile_path, "r") as configfile: + configfile_data = json.load(configfile) + self.branding = configfile_data.get("branding", "") # Sets up self.branding for outputting the name stored in the "confgnew.json" file, as used in print statements. + self.params = configfile_data # Loads in the curent "confgnew.json" file to self.params. + self.debug_tts = configfile_data.get("debugging").get("debug_tts") # Can be used within this script as a True/False flag for generally debugging the TTS generation process. + self.debug_tts_variables = configfile_data.get("debugging").get("debug_tts_variables") # Can be used within this script as a True/False flag for generally debugging variables (if you wish). + ################################################################ + # DONT CHANGE # Print out Python, CUDA, DeepSpeed versions #### + ################################################################ + def printout_versions(self): + if deepspeed_available: + print(f"[{self.branding}ENG] \033[92mDeepSpeed version :\033[93m",deepspeed.__version__,"\033[0m") + else: + print(f"[{self.branding}ENG] \033[92mDeepSpeed version :\033[93m Not available\033[0m") + print(f"[{self.branding}ENG] \033[92mPython Version :\033[93m {python_version}\033[0m") + print(f"[{self.branding}ENG] \033[92mPyTorch Version :\033[93m {pytorch_version}\033[0m") + if cuda_version is None: + print(f"[{self.branding}ENG] \033[92mCUDA Version :\033[91m Not available\033[0m") + else: + print(f"[{self.branding}ENG] \033[92mCUDA Version :\033[93m {cuda_version}\033[0m") + print(f"[{self.branding}ENG]") + return + + ################################################################################### + ################################################################################### + # CHANGE ME # Inital setup of the model and engine. Called when the script starts # + ################################################################################### + ################################################################################### + # In here you will add code to load in your model and do its inital setup. So you + # may be calling your model loader via handle_tts_method_change or if your TTS + # engine doesnt actually load a model into CUDA or System RAM, you may be doing + # Something to fake its start-up. + async def setup(self): + self.printout_versions() + self.available_models = self.scan_models_folder() + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + if self.selected_model: + tts_model = f"{self.selected_model}" + if tts_model in self.available_models: + await self.handle_tts_method_change(tts_model) + self.current_model_loaded = tts_model + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + else: + self.current_model_loaded = "No Models Available" + print(f"[{self.branding}ENG] \033[91mError\033[0m: Selected model '{self.selected_model}' not found in the models folder.") + self.setup_has_run = True # Set to True, so that the /api/ready endpoint can provide a "Ready" status + + ################################## + ################################## + # CHANGE ME # Low VRAM Swapping # + ################################## + ################################## + # If your model does load into CUDA and you want to support LowVRAM, aka moving the model + # on the fly between CUDA and System RAM on each generation request, you will be adding + # The code in here to do it. See the XTTS tts engine for an example. Piper however, doesnt + # Load into VRAM or System RAM, also low vram is set globally disabled by the model_settings.JSON + # file, so this would never get called anywyay, however, we still need to keep an empty + # function in place. Piper TTS uses "pass" telling function to just exit out cleanly if called. + # However, its quite a simple check along the lines of "if CUDA is available and model is + # in X place, then send it to Y place (or Y to X). + async def handle_lowvram_change(self): + if torch.cuda.is_available(): + if self.device == "cuda": + self.device = "cpu" + self.model.to(self.device) + torch.cuda.empty_cache() + else: + self.device == "cpu" + self.device = "cuda" + self.model.to(self.device) + + ######################################## + ######################################## + # CHANGE ME # DeepSpeed model loading # + ######################################## + ######################################## + # If the model supports CUDA and DeepSpeed, this is where you can handle re-loading + # the model as/when DeepSpeed is enabled/selected in the user interface. If your + # TTS model doesnt support DeepSpeed, then it should be globally set in your + # model_settings.JSON and this function will never get called, however it still needs + # to exist as a function. + async def handle_deepspeed_change(self, value): + if value: + # DeepSpeed enabled + print(f"[{self.branding}ENG] \033[93mDeepSpeed Activating\033[0m") + await self.unload_model() + self.params["tts_method_api_local"] = False + self.params["tts_method_xtts_local"] = True + self.deepspeed_enabled = True + await self.setup() + else: + # DeepSpeed disabled + print(f"[{self.branding}ENG] \033[93mDeepSpeed De-Activating\033[0m") + self.deepspeed_enabled = False + await self.unload_model() + await self.setup() + return value # Return new checkbox value + + ############################################################################################################################################## + ############################################################################################################################################## + # CHANGE ME # scan for available models/voices that are relevant to this TTS engine # XTTS is very unusal as it has 2x model loading methods # + ############################################################################################################################################## + ############################################################################################################################################## + # This function looks and reports back the list of possible models your TTS engine can + # load in. Some TTS engines have multiple models they can load and you will want to use + # code for checking if the models are in the correct location/placement within the disk + # the correct files exst per model etc (check XTTS for an example of this). Some models + # like Piper, the actual models are the voices, so in the Piper scan_models_folder + # function, we fake the only model being available as {'piper': 'piper'} aka, model name + # then engine name, then we use the voices_file_list to populate the models as available + # voices that can be selected in the interface. + # If no models are found, we return "No Models Available" and continue on with the script. + def scan_models_folder(self): + models_folder = self.main_dir / "models" / "xtts" # Edit to match the name of your folder where voices are stored + self.available_models = {} + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + required_files = ["config.json", "model.pth", "mel_stats.pth", "speakers_xtts.pth", "vocab.json", "dvae.pth"] + for subfolder in models_folder.iterdir(): + if subfolder.is_dir(): + model_name = subfolder.name + if all(subfolder.joinpath(file).exists() for file in required_files): + self.available_models[f"xtts - {model_name}"] = "xtts" + self.available_models[f"apitts - {model_name}"] = "apitts" + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + else: + self.available_models = {'No Models Available': 'xtts'} # Edit the xtts to whatever the name of your engine is + print(f"[{self.branding}ENG] \033[91mWarning\033[0m: Model folder '{model_name}' is missing required") + print(f"[{self.branding}ENG] \033[91mWarning\033[0m: files or the folder does not exist.") + return self.available_models + + ############################################################# + ############################################################# + # CHANGE ME # POPULATE FILES LIST FROM VOICES DIRECTORY #### + ############################################################# + ############################################################# + # This function looks and reports back the list of possible voice your TTS engine can + # load in. Some TTS engines the voices are wav file samples (XTTS), some are models + # (Piper) and some are text (Parler) thats stored in a JSON file. We just need to + # populate the "voices" variable somehow and if no voices are found, we return + # "No Voices Found" back to the interface/api. + def voices_file_list(self): + try: + voices = [] + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + directory = self.main_dir / "voices" + voices = [f for f in os.listdir(directory) if os.path.isfile(os.path.join(directory, f)) and f.endswith(".wav")] + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + if not voices: + return ["No Voices Found"] # Return a list with {'No Voices Found'} if there are no voices/voice models. + return voices # Return a list of models in the format {'engine name': 'model 1', 'engine name': 'model 2", etc} + except Exception as e: + print(f"[{self.branding}ENG] \033[91mError\033[0m: Voices/Voice Models not found. Cannot load a list of voices.") + print(f"[{self.branding}ENG]") + return ["No Voices Found"] + + ###################################################################################### + ###################################################################################### + # CHANGE ME # Model loading # XTTS is very unusal as it has 2x model loading methods # + ###################################################################################### + ###################################################################################### + # This function will handle the loading of your model, into VRAM/CUDA, System RAM or whatever. + # In XTTS, which has 2x model loader types, there are 2x loaders. They are called by "def handle_tts_method_change" + # In Piper we fake a model loader as Piper doesnt actually load a model into CUDA/System RAM as such. So, in that + # situation, api_manual_load_model is kind of a blank function. Though we do set self.is_tts_model_loaded = True + # as this is used elsewhere in the scripts to confirm that a model is available to be used for TTS generation. + # We always check for "No Models Available" being sent as that means we are trying to load in a model that + # doesnt exist/wasnt found on script start-up e.g. someone deleted the model from the folder or something. + + # Model loader Method 1 + async def api_manual_load_model(self, model_name): + if "No Models Available" in self.available_models: + print(f"[{self.branding}ENG] \033[91mError\033[0m: No models for this TTS engine were found to load. Please download a model.") + return + model_path = self.main_dir / "models" / "xtts" / model_name + print("model_path is:", model_path) if self.debug_tts_variables else None + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + self.model = TTS( # self.model is a global variable for the variable controlling the model in vram/cuda/system ram + model_path=model_path, + config_path=model_path / "config.json", + ).to(self.device) + print(f"[{self.branding}ENG] \033[94mModel License:\033[93m https://coqui.ai/cpml.txt\033[0m") + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + self.is_tts_model_loaded = True + return self.model + + # Model loader Method 2 + async def xtts_manual_load_model(self, model_name): + if "No Models Available" in self.available_models: + print(f"[{self.branding}ENG] \033[91mError\033[0m: No models for this TTS engine were found to load. Please download a model.") + return + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + config = XttsConfig() + model_path = self.main_dir / "models" / "xtts" / model_name + config_path = model_path / "config.json" + vocab_path_dir = model_path / "vocab.json" + checkpoint_dir = model_path + config.load_json(str(config_path)) + self.model = Xtts.init_from_config(config) + self.model.load_checkpoint( + config, + checkpoint_dir=str(checkpoint_dir), + vocab_path=str(vocab_path_dir), + use_deepspeed=self.deepspeed_enabled, + ) + self.model.to(self.device) + self.is_tts_model_loaded = True + print(f"[{self.branding}ENG] \033[94mModel License:\033[93m https://coqui.ai/cpml.txt\033[0m") + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + return self.model + + ############################### + ############################### + # CHANGE ME # Model unloading # + ############################### + ############################### + # This function will handle the UN-loading of your model, from VRAM/CUDA, System RAM or whatever. + # In XTTS, that model loads into CUDA/System Ram, so when we swap models, we want to unload the current model + # free up the memory and then load in the new model to VRAM/CUDA. On the flip side of that, Piper doesnt + # doesnt load into memory, so we just need to put a fake function here that doesnt really do anything + # other than set "self.is_tts_model_loaded = False", which would be set back to true by the model loader. + # So look at the Piper model_engine.py if you DONT need to unload models. + async def unload_model(self): + self.is_tts_model_loaded = False + if not self.current_model_loaded == None: + print(f"[{self.branding}ENG] \033[94mUnloading model \033[0m") if self.debug_tts else None + if hasattr(self, 'model'): + del self.model + if torch.cuda.is_available(): + torch.cuda.empty_cache() + return None + + ################################################################################################################################### + ################################################################################################################################### + # CHANGE ME # Model changing. Unload out old model and load in a new one # XTTS is very unusal as it has 2x model loading methods # + ################################################################################################################################### + ################################################################################################################################### + # This function is your central model loading/unloading handler that deals with the above functions as necesary, to call loading, unloading, + # swappng DeepSpeed, Low vram etc. This function gets called with a "engine name - model name" type call. In XTTS, because there are 2x + # model loader types, (XTTS and APILocal), we take tts_method and split the "engine name - model name" into a loader type and the model + # that it needs to load in and then we call the correct loader function. Whereas in Piper, which doesnt load models into memory at all, + # we just have a fake function that doesnt really do anything. We always check to see if the model name has "No Models Available" in the + # name thats sent over, just to catch any potential errors. We display the start load time and end load time. Thats about it. + async def handle_tts_method_change(self, tts_method): + generate_start_time = time.time() # Record the start time of loading the model + if "No Models Available" in self.available_models: + print(f"[{self.branding}ENG] \033[91mError\033[0m: No models for this TTS engine were found to load. Please download a model.") + return False + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + await self.unload_model() + if tts_method.startswith("xtts"): + model_name = tts_method.split(" - ")[1] + print(f"[{self.branding}ENG]\033[94m Model/Engine :\033[93m {model_name}\033[94m loading into\033[93m", self.device,"\033[0m") + self.model = await self.xtts_manual_load_model(model_name) + self.current_model_loaded = f"xtts - {model_name}" + elif tts_method.startswith("apitts"): + model_name = tts_method.split(" - ")[1] + print(f"[{self.branding}ENG]\033[94m Model/Engine :\033[93m {model_name}\033[94m loading into\033[93m", self.device,"\033[0m") + self.model = await self.api_manual_load_model(model_name) + self.current_model_loaded = f"apitts - {model_name}" + else: + self.current_model_loaded = None + return False + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + generate_end_time = time.time() # Create an end timer for calculating load times + generate_elapsed_time = generate_end_time - generate_start_time # Calculate start time minus end time + print(f"[{self.branding}ENG] \033[94mLoad time :\033[93m {generate_elapsed_time:.2f} seconds.\033[0m") # Print out the result of the load time + return True + + ########################################################################################################################################## + ########################################################################################################################################## + # CHANGE ME # Model changing. Unload out old model and load in a new one # XTTS is very unusal as it has 2x model TTS generation methods # + ########################################################################################################################################## + ########################################################################################################################################## + # In here all the possible options are sent over (text, voice to use, lanugage, speed etc etc) and its up to you how you use them, or not. + # obviously if your TTS engine doesnt support speed for example, generationspeed_capable should be set False in your model_settings.JSON file + # and a fake "generationspeed_set" value should be set. This allows a fake value to be sent over from the main script, even though it + # wouldnt actually ever be used in the generation below. Nonethless all these values, real or just whats inside the configuration file + # will be sent over for use. + # Setting the xxxxxxx_capabale in the model_settings.JSON file, will enable/disable them being selectable by the user. For example, if you + # set "generationspeed_capable" as false in the model_settings.JSON file, a user will not be able to select OR set the setting for + # generation speed. + # One thing to note is that we HAVE to have something in this generation request that is synchronous from the way its called, which means + # we have to have an option for Streaming, even if our TTS engine doesnt support streaming. So in that case, we would set streaming_capable + # as false in our model_settings.JSON file, meaning streaming will never be called. However, we have to put a fake streaming routine in our + # function below (or a real function if it does support streaming of course). Parler has an example of a fake streaming function, which is + # very clearly highlighted in its model_engine.py script. + # Piper TTS, which uses command line based calls and therefore has different ones for Windows and Linux/Mac, has an example of doing this + # within its model_engine.py file. + async def generate_tts(self, text, voice, language, temperature, repetition_penalty, speed, pitch, output_file, streaming): + print(f"[{self.branding}Debug] Entered model_engine.py generate_tts function") if self.debug_tts else None + if not self.is_tts_model_loaded: # Check if a model is loaded and error out if not. + error_message = f"[{self.branding}ENG] \033[91mError\033[0m: You currently have no TTS model loaded." + print(error_message) + raise HTTPException(status_code=400, detail="You currently have no TTS model loaded.") # Raise an exception with a meaningful HTTP status code + self.tts_generating_lock = True # Set the tts_generating lock to True, which stops other generation requests being sent into the pipeline + print(f"[{self.branding}Debug] Checking low VRAM") if self.debug_tts else None + if self.lowvram_enabled and self.device == "cpu": # If necessary, move the model out of System Ram to VRAM + print(f"[{self.branding}Debug] Switching device") if self.debug_tts else None + await self.handle_lowvram_change() + print(f"[{self.branding}Debug] Setting a generate time") if self.debug_tts else None + generate_start_time = time.time() # Record the start time of generating TTS + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + # ↑↑↑ Keep everything above this line ↑↑↑ + # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + + # XTTSv2 LOCAL & Xttsv2 FT Method + print(f"[{self.branding}Debug] Deciding if streaming or not") if self.debug_tts else None + print(f"[{self.branding}Debug] self.current_model_loaded", self.current_model_loaded) if self.debug_tts else None + self.current_model_loaded + if self.current_model_loaded.startswith ("xtts"): + print(f"[{self.branding}Debug] Text arriving at engine {text}") if self.debug_tts else None + gpt_cond_latent, speaker_embedding = self.model.get_conditioning_latents( + audio_path=[f"{self.main_dir}/voices/{voice}"], + gpt_cond_len=self.model.config.gpt_cond_len, + max_ref_length=self.model.config.max_ref_len, + sound_norm_refs=self.model.config.sound_norm_refs, + ) + print(f"[{self.branding}Debug] Moving to common args") if self.debug_tts else None + # Common arguments for both functions + common_args = { + "text": text, + "language": language, + "gpt_cond_latent": gpt_cond_latent, + "speaker_embedding": speaker_embedding, + "temperature": float(temperature), + "length_penalty": float(self.model.config.length_penalty), + "repetition_penalty": float(repetition_penalty), + "top_k": int(self.model.config.top_k), + "top_p": float(self.model.config.top_p), + "speed": float(speed), + "enable_text_splitting": True + } + print(f"[{self.branding}Debug] Common arguments: {common_args}") if self.debug_tts_variables else None + #tts_stop_generation = False # Called to stop generation of the current text at whatever stage its at. currently only set for streaming. + # Determine the correct inference function and add streaming specific argument if needed + inference_func = self.model.inference_stream if streaming else self.model.inference + if streaming: + common_args["stream_chunk_size"] = 20 + + # Call the appropriate function + output = inference_func(**common_args) + + # Process the output based on streaming or non-streaming + if streaming: + print(f"[{self.branding}Debug] Streaming audio generation started") if self.debug_tts else None + # Streaming-specific operations + file_chunks = [] + wav_buf = io.BytesIO() + with wave.open(wav_buf, "wb") as vfout: + vfout.setnchannels(1) + vfout.setsampwidth(2) + vfout.setframerate(24000) + vfout.writeframes(b"") + wav_buf.seek(0) + yield wav_buf.read() + + for i, chunk in enumerate(output): + if self.tts_stop_generation: # This is the variable that is called by the stop generation endpoint and interrupts streaming chunk generation when set True. + print(f"[{self.branding}GEN] Stopping audio generation.") if self.debug_tts else None + file_chunks.clear() # Clear the file_chunks list + self.tts_stop_generation = False # Set the stop back to false + self.tts_generating_lock = False # Undo the generation lock + break # Exit out of the function + file_chunks.append(chunk) + if isinstance(chunk, list): + chunk = torch.cat(chunk, dim=0) + chunk = chunk.clone().detach().cpu().numpy() + chunk = chunk[None, : int(chunk.shape[0])] + chunk = np.clip(chunk, -1, 1) + chunk = (chunk * 32767).astype(np.int16) + yield chunk.tobytes() + print(f"[{self.branding}Debug] Yielded audio chunk {i}") if self.debug_tts else None + print(f"[{self.branding}Debug] Streaming audio generation completed") if self.debug_tts else None + else: + print(f"[{self.branding}Debug] Non-streaming audio generation", output_file) if self.debug_tts else None + torchaudio.save(output_file, torch.tensor(output["wav"]).unsqueeze(0), 24000) + print(f"[{self.branding}Debug] Non-streaming audio generation completed") if self.debug_tts else None + + # apitts Methods + elif self.current_model_loaded.startswith ("apitts"): + # Streaming only allowed for XTTSv2 local + if streaming: + raise ValueError("Streaming is only supported in XTTSv2 local") + + # Set the correct output path (different from the if statement) + print(f"[{self.branding}Debug] Using apitts") if self.debug_tts else None + self.model.tts_to_file( + text=text, + file_path=output_file, + speaker_wav=[f"{self.main_dir}/voices/{voice}"], + language=language, + temperature=temperature, + length_penalty=self.model.config.length_penalty, + repetition_penalty=repetition_penalty, + top_k=self.model.config.top_k, + top_p=self.model.config.top_p, + speed=speed, + ) + + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + # ↓↓↓ Keep everything below this line ↓↓↓ + # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + generate_end_time = time.time() # Record the end time to generate TTS + generate_elapsed_time = generate_end_time - generate_start_time + print(f"[{self.branding}GEN] \033[94mTTS Generate: \033[93m{generate_elapsed_time:.2f} seconds. \033[94mLowVRAM: \033[33m{self.lowvram_enabled} \033[94mDeepSpeed: \033[33m{self.deepspeed_enabled}\033[0m") + if self.lowvram_enabled and self.device == "cuda" and self.tts_narrator_generatingtts == False: + await self.handle_lowvram_change() + self.tts_generating_lock = False # Unlock the TTS generation queue to allow TTS generation requests to come in again. + print(f"[{self.branding}Debug] generate_tts function completed") if self.debug_tts else None + return + + diff --git a/system/tts_engines/xtts/model_settings.json b/system/tts_engines/xtts/model_settings.json new file mode 100644 index 0000000..5e909e5 --- /dev/null +++ b/system/tts_engines/xtts/model_settings.json @@ -0,0 +1,42 @@ +{ + "model_details": { + "manufacturer_name": "Coqui", + "manufacturer_website": "https://github.com/coqui-ai/TTS and also updated engine https://github.com/idiap/coqui-ai-TTS/", + "model_description": "XTTS is a multi-speaker, voice cloning model. XTTS-v2 by Coqui AI is a voice generation model that lets you clone voices into a multitude of languages by using just a mere 6+ second audio sample. It is licenced under Coqui AI's Coqui Public Model License 1.0.0 which allows for `non-commercial use of a machine learning model and its outputs.`" + }, + "model_capabilties": { + "audio_format": "wav", + "deepspeed_capable": true, + "generationspeed_capable": true, + "languages_capable": true, + "lowvram_capable": true, + "multimodel_capable": true, + "repetitionpenalty_capable": true, + "streaming_capable": true, + "temperature_capable": true, + "multivoice_capable": true, + "pitch_capable": false, + "windows_capable": true, + "linux_capable": true, + "mac_capable": true + }, + "settings": { + "def_character_voice": "female_01.wav", + "def_narrator_voice": "male_01.wav", + "deepspeed_enabled": false, + "engine_installed": true, + "generationspeed_set": 1, + "lowvram_enabled": false, + "pitch_set": 0, + "repetitionpenalty_set": 10, + "temperature_set": 0.75 + }, + "openai_voices": { + "alloy": "female_01.wav", + "echo": "female_01.wav", + "fable": "female_01.wav", + "nova": "female_01.wav", + "onyx": "female_01.wav", + "shimmer": "female_01.wav" + } +} \ No newline at end of file diff --git a/system/tts_engines/xtts/xtts_settings_page.py b/system/tts_engines/xtts/xtts_settings_page.py new file mode 100644 index 0000000..a547aa4 --- /dev/null +++ b/system/tts_engines/xtts/xtts_settings_page.py @@ -0,0 +1,348 @@ +import os +import json +import requests +import gradio as gr +from tqdm import tqdm +from pathlib import Path + +this_dir = Path(__file__).parent.resolve() # Sets up self.this_dir as a variable for the folder THIS script is running in. +main_dir = Path(__file__).parent.parent.parent.parent.resolve() # Sets up self.main_dir as a variable for the folder AllTalk is running in + +########################################################################## +# REQUIRED CHANGE # +# Populate the voices list, using the method specific to your TTS engine # +########################################################################## +# This function is responsible for populating the list of available voices for your TTS engine. +# You need to modify this function to use the appropriate method for your engine to retrieve the voice list. +# +# The current implementation lists all the WAV files in a "voices" directory, which may not be suitable for your engine. +# You should replace the `xxxx_voices_file_list` function name to match your engine name. For example, if your engine +# is named "mytts", the function should be named `mytts_voices_file_list`. +# +# You will also neef to update the code with your own implementation that retrieves the voice list according to your +# engine's specific requirements. Typically this is the same code as will be in your model_engine.py file. +# +# For example, if your engine has a dedicated API or configuration file for managing voices, you should modify this +# function to interact with that API or read from that configuration file. +# +# After making the necessary changes, this function should return a list of available voices that can be used +# in your TTS engine's settings page. + +def xtts_voices_file_list(): + directory = main_dir / "voices" + files = [f for f in os.listdir(directory) if os.path.isfile(os.path.join(directory, f)) and f.endswith(".wav")] + return files + +###################################################### +# REQUIRED CHANGE # +# Imports and saves the TTS engine-specific settings # +###################################################### +# This function is responsible for importing and saving the settings specific to your TTS engine. +# You need to make the following change: +# +# 1. Change the name of the function `xxxx_model_update_settings` to match your engine's name. +# For example, if your engine is named "mytts", the function should be named `mytts_model_update_settings`. +# +# After making this change, the function will load the model settings from a JSON file, update the settings and voice +# dictionaries with the values provided as arguments, and save the updated settings back to the JSON file. +# +# You do not need to modify the function's logic or any other part of the code. + +def xtts_model_update_settings(def_character_voice_gr, def_narrator_voice_gr, lowvram_enabled_gr, deepspeed_enabled_gr, temperature_set_gr, repetitionpenalty_set_gr, pitch_set_gr, generationspeed_set_gr, alloy_gr, echo_gr, fable_gr, nova_gr, onyx_gr, shimmer_gr): + # Load the model_config_data from the JSON file + with open(os.path.join(this_dir, "model_settings.json"), "r") as f: + model_config_data = json.load(f) + + # Update the settings and openai_voices dictionaries with the new values + model_config_data["settings"]["def_character_voice"] = def_character_voice_gr + model_config_data["settings"]["def_narrator_voice"] = def_narrator_voice_gr + model_config_data["openai_voices"]["alloy"] = alloy_gr + model_config_data["openai_voices"]["echo"] = echo_gr + model_config_data["openai_voices"]["fable"] = fable_gr + model_config_data["openai_voices"]["nova"] = nova_gr + model_config_data["openai_voices"]["onyx"] = onyx_gr + model_config_data["openai_voices"]["shimmer"] = shimmer_gr + model_config_data["settings"]["lowvram_enabled"] = lowvram_enabled_gr == "Enabled" + model_config_data["settings"]["deepspeed_enabled"] = deepspeed_enabled_gr == "Enabled" + model_config_data["settings"]["temperature_set"] = temperature_set_gr + model_config_data["settings"]["repetitionpenalty_set"] = repetitionpenalty_set_gr + model_config_data["settings"]["pitch_set"] = pitch_set_gr + model_config_data["settings"]["generationspeed_set"] = generationspeed_set_gr + + # Save the updated model_config_data to the JSON file + with open(os.path.join(this_dir, "model_settings.json"), "w") as f: + json.dump(model_config_data, f, indent=4) + + return "Settings updated successfully!" + +####################################################### +# REQUIRED CHANGE # +# Sets up the engine-specific settings page in Gradio # +####################################################### +# This function sets up the Gradio interface for the settings page specific to your TTS engine. +# You need to make the following changes: +# +# 1. Change the name of the function `xxxx_model_alltalk_settings` to match your engine's name. +# For example, if your engine is named "mytts", the function should be named `mytts_model_alltalk_settings`. +# +# 2. Change the name of the `submit_button.click` function call to match the name you gave to the function +# that imports and saves your engine's settings (the function you modified above). +# +# 3. Change the name of the `voice_list` function call to match the name of the function that lists +# the available voices for your TTS engine. +# +# 4. Change the 'title' of the `gr.Blocks` to match your engine's name e.g. title="mytts TTS" +# +# After making these changes, this function will create and return the Gradio interface for your TTS engine's +# settings page, allowing users to configure various options and voice selections. + +def xtts_model_alltalk_settings(model_config_data): + features_list = model_config_data['model_capabilties'] + voice_list = xtts_voices_file_list() + with gr.Blocks(title="Xtts TTS") as app: + with gr.Tab("Default Settings"): + with gr.Row(): + lowvram_enabled_gr = gr.Radio(choices={"Enabled": "true", "Disabled": "false"}, label="Low VRAM" if model_config_data["model_capabilties"]["lowvram_capable"] else "Low VRAM N/A", value="Enabled" if model_config_data["settings"]["lowvram_enabled"] else "Disabled", interactive=model_config_data["model_capabilties"]["lowvram_capable"]) + deepspeed_enabled_gr = gr.Radio(choices={"Enabled": "true", "Disabled": "false"}, label="DeepSpeed Activate" if model_config_data["model_capabilties"]["deepspeed_capable"] else "DeepSpeed N/A", value="Enabled" if model_config_data["settings"]["deepspeed_enabled"] else "Disabled", interactive=model_config_data["model_capabilties"]["deepspeed_capable"]) + temperature_set_gr = gr.Slider(value=float(model_config_data["settings"]["temperature_set"]), minimum=0, maximum=1, step=0.05, label="Temperature" if model_config_data["model_capabilties"]["temperature_capable"] else "Temperature N/A", interactive=model_config_data["model_capabilties"]["temperature_capable"]) + repetitionpenalty_set_gr = gr.Slider(value=float(model_config_data["settings"]["repetitionpenalty_set"]), minimum=1, maximum=20, step=1, label="Repetition Penalty" if model_config_data["model_capabilties"]["repetitionpenalty_capable"] else "Repetition N/A", interactive=model_config_data["model_capabilties"]["repetitionpenalty_capable"]) + pitch_set_gr = gr.Slider(value=float(model_config_data["settings"]["pitch_set"]), minimum=-10, maximum=10, step=1, label="Pitch" if model_config_data["model_capabilties"]["pitch_capable"] else "Pitch N/A", interactive=model_config_data["model_capabilties"]["pitch_capable"]) + generationspeed_set_gr = gr.Slider(value=float(model_config_data["settings"]["generationspeed_set"]), minimum=0.25, maximum=2.00, step=0.25, label="Speed" if model_config_data["model_capabilties"]["generationspeed_capable"] else "Speed N/A", interactive=model_config_data["model_capabilties"]["generationspeed_capable"]) + with gr.Row(): + with gr.Column(): + gr.Markdown("### OpenAI Voice Mappings") + with gr.Group(): + with gr.Row(): + alloy_gr = gr.Dropdown(value=model_config_data["openai_voices"]["alloy"], label="Alloy", choices=voice_list, allow_custom_value=True) + echo_gr = gr.Dropdown(value=model_config_data["openai_voices"]["echo"], label="Echo", choices=voice_list, allow_custom_value=True) + with gr.Row(): + fable_gr = gr.Dropdown(value=model_config_data["openai_voices"]["fable"], label="Fable", choices=voice_list, allow_custom_value=True) + nova_gr = gr.Dropdown(value=model_config_data["openai_voices"]["nova"], label="Nova", choices=voice_list, allow_custom_value=True) + with gr.Row(): + onyx_gr = gr.Dropdown(value=model_config_data["openai_voices"]["onyx"], label="Onyx", choices=voice_list, allow_custom_value=True) + shimmer_gr = gr.Dropdown(value=model_config_data["openai_voices"]["shimmer"], label="Shimmer", choices=voice_list, allow_custom_value=True) + with gr.Column(): + gr.Markdown("### Default Voices") + with gr.Row(): + def_character_voice_gr = gr.Dropdown(value=model_config_data["settings"]["def_character_voice"], label="Default/Character Voice", choices=voice_list, allow_custom_value=True) + def_narrator_voice_gr = gr.Dropdown(value=model_config_data["settings"]["def_narrator_voice"], label="Narrator Voice", choices=voice_list, allow_custom_value=True) + with gr.Group(): + with gr.Row(): + details_text = gr.Textbox(label="Details", show_label=False, lines=5, interactive=False, value="In this section, you can set the default settings for this TTS engine. Settings that are not supported by the current engine will be greyed out and cannot be selected. Default voices specified here will be used when no specific voice is provided in the TTS generation request. If a voice is specified in the request, it will override these default settings. When using the OpenAI API compatable API with this TTS engine, the voice mappings will be applied. As the OpenAI API has a limited set of 6 voices, these mappings ensure compatibility by mapping the OpenAI voices to the available voices in this TTS engine.") + with gr.Row(): + submit_button = gr.Button("Update Settings") + output_message = gr.Textbox(label="Output Message", interactive=False, show_label=False) + + submit_button.click(xtts_model_update_settings, inputs=[def_character_voice_gr, def_narrator_voice_gr, lowvram_enabled_gr, deepspeed_enabled_gr, temperature_set_gr, repetitionpenalty_set_gr, pitch_set_gr, generationspeed_set_gr, alloy_gr, echo_gr, fable_gr, nova_gr, onyx_gr, shimmer_gr], outputs=output_message) + + ########################################################################################### + # Do not change this section apart from "TTS Engine Name" value to match your engine name # + ########################################################################################### + with gr.Tab("Engine Information"): + with gr.Row(): + with gr.Group(): + gr.Textbox(label="Manufacturer Name", value=model_config_data['model_details']['manufacturer_name'], interactive=False) + gr.Textbox(label="Manufacturer Website/TTS Engine Support", value=model_config_data['model_details']['manufacturer_website'], interactive=False) + gr.Textbox(label="Engine/Model Description", value=model_config_data['model_details']['model_description'], interactive=False, lines=13) + with gr.Column(): + with gr.Row(): + gr.Textbox(label="DeepSpeed Capable", value='Yes' if features_list['deepspeed_capable'] else 'No', interactive=False) + gr.Textbox(label="Pitch Capable", value='Yes' if features_list['pitch_capable'] else 'No', interactive=False) + gr.Textbox(label="Generation Speed Capable", value='Yes' if features_list['generationspeed_capable'] else 'No', interactive=False) + with gr.Row(): + gr.Textbox(label="Repetition Penalty Capable", value='Yes' if features_list['repetitionpenalty_capable'] else 'No', interactive=False) + gr.Textbox(label="Multi Languages Capable", value='Yes' if features_list['languages_capable'] else 'No', interactive=False) + gr.Textbox(label="Streaming Capable", value='Yes' if features_list['streaming_capable'] else 'No', interactive=False) + with gr.Row(): + gr.Textbox(label="Low VRAM Capable", value='Yes' if features_list['lowvram_capable'] else 'No', interactive=False) + gr.Textbox(label="Temperature Capable", value='Yes' if features_list['temperature_capable'] else 'No', interactive=False) + gr.Textbox(label="Multi Model Capable Engine", value='Yes' if features_list['multimodel_capable'] else 'No', interactive=False) + with gr.Row(): + gr.Textbox(label="Multi Voice Capable Models", value='Yes' if features_list['multivoice_capable'] else 'No', interactive=False) + gr.Textbox(label="Default Audio output format", value=model_config_data['model_capabilties']['audio_format'], interactive=False) + gr.Textbox(label="TTS Engine Name", value="XTTS", interactive=False) + with gr.Row(): + gr.Textbox(label="Windows Support", value='Yes' if features_list['windows_capable'] else 'No', interactive=False) + gr.Textbox(label="Linux Support", value='Yes' if features_list['linux_capable'] else 'No', interactive=False) + gr.Textbox(label="Mac Support", value='Yes' if features_list['mac_capable'] else 'No', interactive=False) + with gr.Row(): + gr.Markdown(""" + #### 🟧 DeepSpeed Capable + DeepSpeed is a deep learning optimization library that can significantly speed up model training and inference. If a model is DeepSpeed capable, it means it can utilize DeepSpeed to accelerate the generation of text-to-speech output. This requires the model to be loaded into CUDA/VRAM on an Nvidia GPU and the model's inference method to support DeepSpeed. + #### 🟧 Pitch Capable + Pitch refers to the highness or lowness of a sound. If a model is pitch capable, it can adjust the pitch of the generated speech, allowing for more expressive and varied output. + #### 🟧 Generation Speed Capable + Generation speed refers to the rate at which the model can generate text-to-speech output. If a model is generation speed capable, it means the speed of the generated speech can be adjusted, making it faster or slower depending on the desired output. + #### 🟧 Repetition Penalty Capable + Repetition penalty is a technique used to discourage the model from repeating the same words or phrases multiple times in the same sounding way. If a model is repetition penalty capable, it can apply this penalty during generation to improve the diversity and naturalness of the output. + #### 🟧 Multi-Languages Capable + Multi-language capable models can generate speech in multiple languages. This means that the model has been trained on data from different languages and can switch between them during generation. Some models are language-specific. + #### 🟧 Multi-Voice Capable + Multi-voice capable models generate speech in multiple voices or speaking styles. This means that the model has been trained on data from different speakers and can mimic their voices during generation, or is a voice cloning model that can generate speech based on the input sample. + """) + gr.Markdown(""" + #### 🟧 Streaming Capable + Streaming refers to the ability to generate speech output in real-time, without the need to generate the entire output before playback. If a model is streaming capable, it can generate speech on-the-fly, allowing for faster response times and more interactive applications. + #### 🟧 Low VRAM Capable + VRAM (Video Random Access Memory) is a type of memory used by GPUs to store and process data. If a model is low VRAM capable, it can efficiently utilize the available VRAM by moving data between CPU and GPU memory as needed, allowing for generation even on systems with limited VRAM where it may be competing with an LLM model for VRAM. + #### 🟧 Temperature Capable + Temperature is a hyperparameter that controls the randomness of the generated output. If a model is temperature capable, the temperature can be adjusted to make the output more or less random, affecting the creativity and variability of the generated speech. + #### 🟧 Multi-Model Capable Engine + If an engine is multi-model capable, it means that it can support and utilize multiple models for text-to-speech generation. This allows for greater flexibility and the ability to switch between different models depending on the desired output. Different models may be capable of different languages, specific languages, voices, etc. + #### 🟧 Default Audio Output Format + Specifies the file format in which the generated speech will be saved. Common audio formats include WAV, MP3, FLAC, Opus, AAC, and PCM. If you want different outputs, you can set the transcode function to change the output audio, though transcoding will add a little time to the generation and is not available for streaming generation. + #### 🟧 Windows/Linux/Mac Support + These indicators show whether the model and engine are compatible with Windows, Linux, or macOS. However, additional setup or requirements may be necessary to ensure full functionality on your operating system. Please note that full platform support has not been extensively tested. + """) + + + with gr.Tab("Models/Voices Download"): + with gr.Row(): + # Load the available models from the JSON file + with open(os.path.join(this_dir, "available_models.json"), "r") as f: + available_models = json.load(f) + # Extract the model names for the dropdown + model_names = [model["model_name"] for model in available_models["models"]] + # Create the dropdown + model_dropdown = gr.Dropdown(choices=sorted(model_names), label="Select Model", value=model_names[0]) + download_button = gr.Button("Download Model/Missing Files") + + with gr.Row(): + download_status = gr.Textbox(label="Download Status") + + def download_model(model_name): + # Find the selected model in the available models + selected_model = next(model for model in available_models["models"] if model["model_name"] == model_name) + + # Get the folder path and files to download + folder_path = os.path.join(main_dir, "models", "xtts", selected_model["folder_path"]) + files_to_download = selected_model["files_to_download"] + + # Check if all files are already downloaded + all_files_exists = all(os.path.exists(os.path.join(folder_path, file)) for file in files_to_download) + + if all_files_exists: + return "All files are already downloaded. No need to download again." + else: + # Create the folder if it doesn't exist + os.makedirs(folder_path, exist_ok=True) + + # Download the missing files + for file, url in files_to_download.items(): + file_path = os.path.join(folder_path, file) + if not os.path.exists(file_path): + print(f"Downloading {file}...") + + response = requests.get(url, stream=True) + total_size_in_bytes = int(response.headers.get("content-length", 0)) + block_size = 1024 # 1 Kibibyte + progress_bar = tqdm(total=total_size_in_bytes, unit="iB", unit_scale=True) + + with open(file_path, "wb") as file: + for data in response.iter_content(block_size): + progress_bar.update(len(data)) + file.write(data) + + progress_bar.close() + + return "Model downloaded successfully!" + + download_button.click(download_model, inputs=model_dropdown, outputs=download_status) + + def show_confirm_cancel(model_name): + all_files_exists = all(os.path.exists(os.path.join(main_dir, "models", "xtts", model["folder_path"], file)) for model in available_models["models"] if model["model_name"] == model_name for file in model["files_to_download"]) + + if all_files_exists: + return [gr.update(visible=True), gr.update(visible=True), gr.update(visible=True)] + else: + return [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)] + + def confirm_download(model_name): + download_model(model_name) + return [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)] + + with gr.Row(): + confirm_button = gr.Button("Download Anyway", visible=False) + cancel_button = gr.Button("Cancel", visible=False) + + download_button.click(show_confirm_cancel, model_dropdown, [confirm_button, download_button, cancel_button]) + confirm_button.click(confirm_download, model_dropdown, [confirm_button, download_button, cancel_button]) + cancel_button.click(lambda: [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)], None, [confirm_button, download_button, cancel_button]) + + ################################################################################################### + # REQUIRED CHANGE # + # Add any engine specific help, bugs, issues, operating system specifc requirements/setup in here # + # Please use Markdown format, so gr.Markdown() with your markdown inside it. # + ################################################################################################### + with gr.Tab("Engine Help"): + with gr.Row(): + gr.Markdown(""" + ### 🟧 Using my own Finetuned models + Please your Finetuned models within their own folder in `/alltalk_tts/models/xtts/`. Once they are in here they will become availabe within the interface after reloading the XTTS engine.
+ + ### 🟧 Using Voice Samples + Voice samples are stored in `/alltalk_tts/voices/` and should be named using the following format `name.wav`
+ + ### 🟧 Where are the outputs stored & Automatic output wav file deletion + Voice outputs are stored in `/alltalk_tts/outputs/`. You can configure automatic maintenance deletion of old wav files by setting `Del WAV's older than` in the global settings.
+ + > When `Disabled`, your output wav files will be left untouched.
+ > When set to a setting `1 Day` or greater, your output wav files older than that time period will be automatically deleted on start-up of AllTalk.
+ + ### 🟧 Where are the models stored? + This extension will download the models to `/alltalk_tts/models/xtts/` folder.
+ + ### 🟧 How do I create a new voice sample? + To create a new voice sample, you need to make a wav file that is `22050Hz`, `Mono`, `16 bit` and between 6 to 30 seconds long, though 8 to 10 seconds is usually good enough. The model can handle up to 30 second samples, however I've not noticed any improvement in voice output from much longer clips.

+ You want to find a nice clear selection of audio, so lets say you wanted to clone your favourite celebrity. You may go looking for an interview where they are talking. Pay close attention to the audio you are listening to and trying to sample. Are there noises in the background, hiss on the soundtrack, a low humm, some quiet music playing or something? The better quality the audio the better the final TTS result. Don't forget, the AI that processes the sounds can hear everything in your sample and it will use them in the voice its trying to recreate.

+ Try make your clip one of nice flowing speech, like the included example files. No big pauses, gaps or other sounds. Preferably a sample that the person you are trying to copy will show a little vocal range and emotion in their voice. Also, try to avoid a clip starting or ending with breathy sounds (breathing in/out etc).
+ """) + gr.Markdown(""" + ### 🟧 Editing your sample! + So, you've downloaded your favourite celebrity interview off YouTube, from here you need to chop it down to 6 to 30 seconds in length and resample it. If you need to clean it up, do audio processing, volume level changes etc, do this before down-sampling.

+ Using the latest version of Audacity `select/highlight` your 6 to 30 second clip and:

+         • `Tracks` > `Resample to 22050Hz`
+         • `Tracks` > `Mix` > `Stereo to Mono`
+         • `File` > `Export Audio` saving it as a `WAV` of `22050Hz`.

+ Save your generated wav file in the `/alltalk_tts/voices/` folder.
+ + ### 🟧 Why doesnt it sound like XXX Person? + Maybe you might be interested in trying Finetuning of the XTTS model. Otherwise, the reasons can be that you:
+ + > Didn't down-sample it as above.
+ > Have a bad quality voice sample.
+ > Try using the 2x different generation methods `API Local` and `XTTSv2 Local`, as they generate output in slightly different ways.
+ + Additionally, use the RVC pipeline with a matching voice model, however, some samples just never seem to work correctly, so maybe try a different sample. Always remember though, this is an AI model attempting to re-create a voice, so you will never get a 100% match. + + ### 🟧 API Local, XTTSv2 Generation methods & Speed + These two methods both produce sound output in slightly different ways. XTTSv2 is the perferable method a it supports DeepSpeed, which, if you have a system capable of DeepSpeed genereation, can result in a 2-3x speed gain in generation. + """) + + return app + +################################ +# REQUIRED CHANGE # +# Sets up the Gradio interface # +################################ +# This function sets up the Gradio interface for your TTS engine's settings page. +# You need to change the name of the function calls to match the names you set in the functions above. +# +# Specifically, you need to update the following: +# +# 1. The name of the function `xxxx_at_gradio_settings_page` to match your engine's name. +# For example, if your engine is named "mytts", the function should be named `mytts_at_gradio_settings_page`. +# +# 2. The name of the function call `xxxx_model_alltalk_settings(model_config_data)`. +# This should match the name you gave to the function that sets up the engine-specific settings page in Gradio. +# If you named that function `mytts_model_alltalk_settings`, then the call should be: +# `mytts_model_alltalk_settings(model_config_data)` +# +# After making these changes, this function will create and return the Gradio app for your TTS engine's settings page. + +def xtts_at_gradio_settings_page(model_config_data): + app = xtts_model_alltalk_settings(model_config_data) + return app +def xtts_at_gradio_settings_page(model_config_data): + app = xtts_model_alltalk_settings(model_config_data) + return app \ No newline at end of file diff --git a/system/tts_generator/crunker.d.ts b/system/tts_generator/crunker.d.ts new file mode 100644 index 0000000..4422095 --- /dev/null +++ b/system/tts_generator/crunker.d.ts @@ -0,0 +1,179 @@ +export interface CrunkerConstructorOptions { + /** + * Sample rate for Crunker's internal audio context. + * + * @default 44100 + */ + sampleRate: number; +} +export type CrunkerInputTypes = string | File | Blob; +/** + * An exported Crunker audio object. + */ +export interface ExportedCrunkerAudio { + blob: Blob; + url: string; + element: HTMLAudioElement; +} +/** + * Crunker is the simple way to merge, concatenate, play, export and download audio files using the Web Audio API. + */ +export default class Crunker { + private readonly _sampleRate; + private readonly _context; + /** + * Creates a new instance of Crunker with the provided options. + * + * If `sampleRate` is not defined, it will auto-select an appropriate sample rate + * for the device being used. + */ + constructor({ sampleRate }?: Partial); + /** + * Creates Crunker's internal AudioContext. + * + * @internal + */ + private _createContext; + /** + * + * The internal AudioContext used by Crunker. + */ + get context(): AudioContext; + /** + * Asynchronously fetches multiple audio files and returns an array of AudioBuffers. + */ + fetchAudio(...filepaths: CrunkerInputTypes[]): Promise; + /** + * Merges (layers) multiple AudioBuffers into a single AudioBuffer. + * + * **Visual representation:** + * + * ![](https://user-images.githubusercontent.com/12958674/88806278-968f0680-d186-11ea-9cb5-8ef2606ffcc7.png) + */ + mergeAudio(buffers: AudioBuffer[]): AudioBuffer; + /** + * Concatenates multiple AudioBuffers into a single AudioBuffer. + * + * **Visual representation:** + * + * ![](https://user-images.githubusercontent.com/12958674/88806297-9d1d7e00-d186-11ea-8cd2-c64cb0324845.png) + */ + concatAudio(buffers: AudioBuffer[]): AudioBuffer; + /** + * Pads a specified AudioBuffer with silence from a specified start time, + * for a specified length of time. + * + * Accepts float values as well as whole integers. + * + * @param buffer AudioBuffer to pad + * @param padStart Time to start padding (in seconds) + * @param seconds Duration to pad for (in seconds) + */ + padAudio(buffer: AudioBuffer, padStart?: number, seconds?: number): AudioBuffer; + /** + * Slices an AudioBuffer from the specified start time to the end time, with optional fade in and out. + * + * @param buffer AudioBuffer to slice + * @param start Start time (in seconds) + * @param end End time (in seconds) + * @param fadeIn Fade in duration (in seconds, default is 0) + * @param fadeOut Fade out duration (in seconds, default is 0) + */ + sliceAudio(buffer: AudioBuffer, start: number, end: number, fadeIn?: number, fadeOut?: number): AudioBuffer; + /** + * Plays the provided AudioBuffer in an AudioBufferSourceNode. + */ + play(buffer: AudioBuffer): AudioBufferSourceNode; + /** + * Exports the specified AudioBuffer to a Blob, Object URI and HTMLAudioElement. + * + * Note that changing the MIME type does not change the actual file format. The + * file format will **always** be a WAVE file due to how audio is stored in the + * browser. + * + * @param buffer Buffer to export + * @param type MIME type (default: `audio/wav`) + */ + export(buffer: AudioBuffer, type?: string): ExportedCrunkerAudio; + /** + * Downloads the provided Blob. + * + * @param blob Blob to download + * @param filename An optional file name to use for the download (default: `crunker`) + */ + download(blob: Blob, filename?: string): HTMLAnchorElement; + /** + * Executes a callback if the browser does not support the Web Audio API. + * + * Returns the result of the callback, or `undefined` if the Web Audio API is supported. + * + * @param callback callback to run if the browser does not support the Web Audio API + */ + notSupported(callback: () => T): T | undefined; + /** + * Closes Crunker's internal AudioContext. + */ + close(): this; + /** + * Returns the largest duration of the longest AudioBuffer. + * + * @internal + */ + private _maxDuration; + /** + * Returns the largest number of channels in an array of AudioBuffers. + * + * @internal + */ + private _maxNumberOfChannels; + /** + * Returns the sum of the lengths of an array of AudioBuffers. + * + * @internal + */ + private _totalLength; + /** + * Returns whether the browser supports the Web Audio API. + * + * @internal + */ + private _isSupported; + /** + * Writes the WAV headers for the specified Float32Array. + * + * Returns a DataView containing the WAV headers and file content. + * + * @internal + */ + private _writeHeaders; + /** + * Converts a Float32Array to 16-bit PCM. + * + * @internal + */ + private _floatTo16BitPCM; + /** + * Writes a string to a DataView at the specified offset. + * + * @internal + */ + private _writeString; + /** + * Converts an AudioBuffer to a Float32Array. + * + * @internal + */ + private _interleave; + /** + * Creates an HTMLAudioElement whose source is the specified Blob. + * + * @internal + */ + private _renderAudioElement; + /** + * Creates an Object URL for the specified Blob. + * + * @internal + */ + private _renderURL; +} diff --git a/system/tts_generator/crunker.esm.js b/system/tts_generator/crunker.esm.js new file mode 100644 index 0000000..a6f5633 --- /dev/null +++ b/system/tts_generator/crunker.esm.js @@ -0,0 +1,2 @@ +var e={d:(t,n)=>{for(var r in n)e.o(n,r)&&!e.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:n[r]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};function n(e){return n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},n(e)}function r(e){return function(e){if(Array.isArray(e))return a(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||function(e,t){if(!e)return;if("string"==typeof e)return a(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return a(e,t)}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function a(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);nu});var u=function(){function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=t.sampleRate;o(this,e),this._context=this._createContext(n),n||(n=this._context.sampleRate),this._sampleRate=n}var t,n,a;return t=e,n=[{key:"_createContext",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:44100;return window.AudioContext=window.AudioContext||window.webkitAudioContext||window.mozAudioContext,new AudioContext({sampleRate:e})}},{key:"context",get:function(){return this._context}},{key:"fetchAudio",value:async function(){for(var e=this,t=arguments.length,n=new Array(t),r=0;r=0;o--)r[o]+=a[o];t.getChannelData(n).set(r)}})),t}},{key:"concatAudio",value:function(e){var t=this._context.createBuffer(this._maxNumberOfChannels(e),this._totalLength(e),this._sampleRate),n=0;return e.forEach((function(e){for(var r=0;r1&&void 0!==arguments[1]?arguments[1]:0,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0;if(0===n)return e;if(t<0)throw new Error('Crunker: Parameter "padStart" in padAudio must be positive');if(n<0)throw new Error('Crunker: Parameter "seconds" in padAudio must be positive');for(var r=this._context.createBuffer(e.numberOfChannels,Math.ceil(e.length+n*e.sampleRate),e.sampleRate),a=0;a3&&void 0!==arguments[3]?arguments[3]:0,a=arguments.length>4&&void 0!==arguments[4]?arguments[4]:0;if(t>=n)throw new Error('Crunker: "start" time should be less than "end" time in sliceAudio method');for(var o=Math.round((n-t)*this._sampleRate),i=Math.round(t*this._sampleRate),u=this._context.createBuffer(e.numberOfChannels,o,this._sampleRate),l=0;lo-a*this._sampleRate&&(c[f]*=(o-f)/(a*this._sampleRate));return u}},{key:"play",value:function(e){var t=this._context.createBufferSource();return t.buffer=e,t.connect(this._context.destination),t.start(),t}},{key:"export",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"audio/wav",n=this._interleave(e),r=this._writeHeaders(n,e.numberOfChannels,e.sampleRate),a=new Blob([r],{type:t});return{blob:a,url:this._renderURL(a),element:this._renderAudioElement(a)}}},{key:"download",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"crunker",n=document.createElement("a");return n.style.display="none",n.href=this._renderURL(e),n.download="".concat(t,".").concat(e.type.split("/")[1]),n.click(),n}},{key:"notSupported",value:function(e){return this._isSupported()?void 0:e()}},{key:"close",value:function(){return this._context.close(),this}},{key:"_maxDuration",value:function(e){return Math.max.apply(Math,r(e.map((function(e){return e.duration}))))}},{key:"_maxNumberOfChannels",value:function(e){return Math.max.apply(Math,r(e.map((function(e){return e.numberOfChannels}))))}},{key:"_totalLength",value:function(e){return e.map((function(e){return e.length})).reduce((function(e,t){return e+t}),0)}},{key:"_isSupported",value:function(){return"AudioContext"in window||"webkitAudioContext"in window||"mozAudioContext"in window}},{key:"_writeHeaders",value:function(e,t,n){var r=2*t,a=2*e.length,o=36+a,i=new ArrayBuffer(8+o),u=new DataView(i);return this._writeString(u,0,"RIFF"),u.setUint32(4,o,!0),this._writeString(u,8,"WAVE"),this._writeString(u,12,"fmt "),u.setUint32(16,16,!0),u.setUint16(20,1,!0),u.setUint16(22,t,!0),u.setUint32(24,n,!0),u.setUint32(28,n*r,!0),u.setUint16(32,r,!0),u.setUint16(34,16,!0),this._writeString(u,36,"data"),u.setUint32(40,a,!0),this._floatTo16BitPCM(u,e,44)}},{key:"_floatTo16BitPCM",value:function(e,t,n){for(var r=0;r {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","export interface CrunkerConstructorOptions {\n /**\n * Sample rate for Crunker's internal audio context.\n *\n * @default 44100\n */\n sampleRate: number;\n}\n\nexport type CrunkerInputTypes = string | File | Blob;\n\n/**\n * An exported Crunker audio object.\n */\nexport interface ExportedCrunkerAudio {\n blob: Blob;\n url: string;\n element: HTMLAudioElement;\n}\n\n/**\n * Crunker is the simple way to merge, concatenate, play, export and download audio files using the Web Audio API.\n */\nexport default class Crunker {\n private readonly _sampleRate: number;\n private readonly _context: AudioContext;\n\n /**\n * Creates a new instance of Crunker with the provided options.\n *\n * If `sampleRate` is not defined, it will auto-select an appropriate sample rate\n * for the device being used.\n */\n constructor({ sampleRate }: Partial = {}) {\n this._context = this._createContext(sampleRate);\n\n sampleRate ||= this._context.sampleRate;\n\n this._sampleRate = sampleRate;\n }\n\n /**\n * Creates Crunker's internal AudioContext.\n *\n * @internal\n */\n private _createContext(sampleRate: number = 44_100): AudioContext {\n window.AudioContext = window.AudioContext || (window as any).webkitAudioContext || (window as any).mozAudioContext;\n return new AudioContext({ sampleRate });\n }\n\n /**\n *\n * The internal AudioContext used by Crunker.\n */\n get context(): AudioContext {\n return this._context;\n }\n\n /**\n * Asynchronously fetches multiple audio files and returns an array of AudioBuffers.\n */\n async fetchAudio(...filepaths: CrunkerInputTypes[]): Promise {\n return await Promise.all(\n filepaths.map(async (filepath) => {\n let buffer: ArrayBuffer;\n\n if (filepath instanceof File || filepath instanceof Blob) {\n buffer = await filepath.arrayBuffer();\n } else {\n buffer = await fetch(filepath).then((response) => {\n if (response.headers.has('Content-Type') && !response.headers.get('Content-Type')!.includes('audio/')) {\n console.warn(\n `Crunker: Attempted to fetch an audio file, but its MIME type is \\`${\n response.headers.get('Content-Type')!.split(';')[0]\n }\\`. We'll try and continue anyway. (file: \"${filepath}\")`\n );\n }\n\n return response.arrayBuffer();\n });\n }\n\n return await this._context.decodeAudioData(buffer);\n })\n );\n }\n\n /**\n * Merges (layers) multiple AudioBuffers into a single AudioBuffer.\n *\n * **Visual representation:**\n *\n * ![](https://user-images.githubusercontent.com/12958674/88806278-968f0680-d186-11ea-9cb5-8ef2606ffcc7.png)\n */\n mergeAudio(buffers: AudioBuffer[]): AudioBuffer {\n const output = this._context.createBuffer(\n this._maxNumberOfChannels(buffers),\n this._sampleRate * this._maxDuration(buffers),\n this._sampleRate\n );\n\n buffers.forEach((buffer) => {\n for (let channelNumber = 0; channelNumber < buffer.numberOfChannels; channelNumber++) {\n const outputData = output.getChannelData(channelNumber);\n const bufferData = buffer.getChannelData(channelNumber);\n\n for (let i = buffer.getChannelData(channelNumber).length - 1; i >= 0; i--) {\n outputData[i] += bufferData[i];\n }\n\n output.getChannelData(channelNumber).set(outputData);\n }\n });\n\n return output;\n }\n\n /**\n * Concatenates multiple AudioBuffers into a single AudioBuffer.\n *\n * **Visual representation:**\n *\n * ![](https://user-images.githubusercontent.com/12958674/88806297-9d1d7e00-d186-11ea-8cd2-c64cb0324845.png)\n */\n concatAudio(buffers: AudioBuffer[]): AudioBuffer {\n const output = this._context.createBuffer(\n this._maxNumberOfChannels(buffers),\n this._totalLength(buffers),\n this._sampleRate\n );\n let offset = 0;\n\n buffers.forEach((buffer) => {\n for (let channelNumber = 0; channelNumber < buffer.numberOfChannels; channelNumber++) {\n output.getChannelData(channelNumber).set(buffer.getChannelData(channelNumber), offset);\n }\n\n offset += buffer.length;\n });\n\n return output;\n }\n\n /**\n * Pads a specified AudioBuffer with silence from a specified start time,\n * for a specified length of time.\n *\n * Accepts float values as well as whole integers.\n *\n * @param buffer AudioBuffer to pad\n * @param padStart Time to start padding (in seconds)\n * @param seconds Duration to pad for (in seconds)\n */\n padAudio(buffer: AudioBuffer, padStart: number = 0, seconds: number = 0): AudioBuffer {\n if (seconds === 0) return buffer;\n\n if (padStart < 0) throw new Error('Crunker: Parameter \"padStart\" in padAudio must be positive');\n if (seconds < 0) throw new Error('Crunker: Parameter \"seconds\" in padAudio must be positive');\n\n const updatedBuffer = this._context.createBuffer(\n buffer.numberOfChannels,\n Math.ceil(buffer.length + seconds * buffer.sampleRate),\n buffer.sampleRate\n );\n\n for (let channelNumber = 0; channelNumber < buffer.numberOfChannels; channelNumber++) {\n const channelData = buffer.getChannelData(channelNumber);\n updatedBuffer\n .getChannelData(channelNumber)\n .set(channelData.subarray(0, Math.ceil(padStart * buffer.sampleRate) + 1), 0);\n\n updatedBuffer\n .getChannelData(channelNumber)\n .set(\n channelData.subarray(Math.ceil(padStart * buffer.sampleRate) + 2, updatedBuffer.length + 1),\n Math.ceil((padStart + seconds) * buffer.sampleRate)\n );\n }\n\n return updatedBuffer;\n }\n\n /**\n * Slices an AudioBuffer from the specified start time to the end time, with optional fade in and out.\n *\n * @param buffer AudioBuffer to slice\n * @param start Start time (in seconds)\n * @param end End time (in seconds)\n * @param fadeIn Fade in duration (in seconds, default is 0)\n * @param fadeOut Fade out duration (in seconds, default is 0)\n */\n sliceAudio(buffer: AudioBuffer, start: number, end: number, fadeIn: number = 0, fadeOut: number = 0): AudioBuffer {\n if (start >= end) throw new Error('Crunker: \"start\" time should be less than \"end\" time in sliceAudio method');\n\n const length = Math.round((end - start) * this._sampleRate);\n const offset = Math.round(start * this._sampleRate);\n const newBuffer = this._context.createBuffer(buffer.numberOfChannels, length, this._sampleRate);\n\n for (let channel = 0; channel < buffer.numberOfChannels; channel++) {\n const inputData = buffer.getChannelData(channel);\n const outputData = newBuffer.getChannelData(channel);\n\n for (let i = 0; i < length; i++) {\n outputData[i] = inputData[offset + i];\n\n // Apply fade in\n if (i < fadeIn * this._sampleRate) {\n outputData[i] *= i / (fadeIn * this._sampleRate);\n }\n\n // Apply fade out\n if (i > length - fadeOut * this._sampleRate) {\n outputData[i] *= (length - i) / (fadeOut * this._sampleRate);\n }\n }\n }\n\n return newBuffer;\n }\n\n /**\n * Plays the provided AudioBuffer in an AudioBufferSourceNode.\n */\n play(buffer: AudioBuffer): AudioBufferSourceNode {\n const source = this._context.createBufferSource();\n\n source.buffer = buffer;\n source.connect(this._context.destination);\n source.start();\n\n return source;\n }\n\n /**\n * Exports the specified AudioBuffer to a Blob, Object URI and HTMLAudioElement.\n *\n * Note that changing the MIME type does not change the actual file format. The\n * file format will **always** be a WAVE file due to how audio is stored in the\n * browser.\n *\n * @param buffer Buffer to export\n * @param type MIME type (default: `audio/wav`)\n */\n export(buffer: AudioBuffer, type: string = 'audio/wav'): ExportedCrunkerAudio {\n const recorded = this._interleave(buffer);\n const dataview = this._writeHeaders(recorded, buffer.numberOfChannels, buffer.sampleRate);\n const audioBlob = new Blob([dataview], { type });\n\n return {\n blob: audioBlob,\n url: this._renderURL(audioBlob),\n element: this._renderAudioElement(audioBlob),\n };\n }\n\n /**\n * Downloads the provided Blob.\n *\n * @param blob Blob to download\n * @param filename An optional file name to use for the download (default: `crunker`)\n */\n download(blob: Blob, filename: string = 'crunker'): HTMLAnchorElement {\n const a = document.createElement('a');\n\n a.style.display = 'none';\n a.href = this._renderURL(blob);\n a.download = `${filename}.${blob.type.split('/')[1]}`;\n a.click();\n\n return a;\n }\n\n /**\n * Executes a callback if the browser does not support the Web Audio API.\n *\n * Returns the result of the callback, or `undefined` if the Web Audio API is supported.\n *\n * @param callback callback to run if the browser does not support the Web Audio API\n */\n notSupported(callback: () => T): T | undefined {\n return this._isSupported() ? undefined : callback();\n }\n\n /**\n * Closes Crunker's internal AudioContext.\n */\n close(): this {\n this._context.close();\n return this;\n }\n\n /**\n * Returns the largest duration of the longest AudioBuffer.\n *\n * @internal\n */\n private _maxDuration(buffers: AudioBuffer[]): number {\n return Math.max(...buffers.map((buffer) => buffer.duration));\n }\n\n /**\n * Returns the largest number of channels in an array of AudioBuffers.\n *\n * @internal\n */\n private _maxNumberOfChannels(buffers: AudioBuffer[]): number {\n return Math.max(...buffers.map((buffer) => buffer.numberOfChannels));\n }\n\n /**\n * Returns the sum of the lengths of an array of AudioBuffers.\n *\n * @internal\n */\n private _totalLength(buffers: AudioBuffer[]): number {\n return buffers.map((buffer) => buffer.length).reduce((a, b) => a + b, 0);\n }\n\n /**\n * Returns whether the browser supports the Web Audio API.\n *\n * @internal\n */\n private _isSupported(): boolean {\n return 'AudioContext' in window || 'webkitAudioContext' in window || 'mozAudioContext' in window;\n }\n\n /**\n * Writes the WAV headers for the specified Float32Array.\n *\n * Returns a DataView containing the WAV headers and file content.\n *\n * @internal\n */\n private _writeHeaders(buffer: Float32Array, numOfChannels: number, sampleRate: number): DataView {\n const bitDepth = 16;\n const bytesPerSample = bitDepth / 8;\n const sampleSize = numOfChannels * bytesPerSample;\n\n const fileHeaderSize = 8;\n const chunkHeaderSize = 36;\n const chunkDataSize = buffer.length * bytesPerSample;\n const chunkTotalSize = chunkHeaderSize + chunkDataSize;\n\n const arrayBuffer = new ArrayBuffer(fileHeaderSize + chunkTotalSize);\n const view = new DataView(arrayBuffer);\n\n this._writeString(view, 0, 'RIFF');\n view.setUint32(4, chunkTotalSize, true);\n this._writeString(view, 8, 'WAVE');\n this._writeString(view, 12, 'fmt ');\n view.setUint32(16, 16, true);\n view.setUint16(20, 1, true);\n view.setUint16(22, numOfChannels, true);\n view.setUint32(24, sampleRate, true);\n view.setUint32(28, sampleRate * sampleSize, true);\n view.setUint16(32, sampleSize, true);\n view.setUint16(34, bitDepth, true);\n this._writeString(view, 36, 'data');\n view.setUint32(40, chunkDataSize, true);\n\n return this._floatTo16BitPCM(view, buffer, fileHeaderSize + chunkHeaderSize);\n }\n\n /**\n * Converts a Float32Array to 16-bit PCM.\n *\n * @internal\n */\n private _floatTo16BitPCM(dataview: DataView, buffer: Float32Array, offset: number): DataView {\n for (let i = 0; i < buffer.length; i++, offset += 2) {\n const tmp = Math.max(-1, Math.min(1, buffer[i]));\n dataview.setInt16(offset, tmp < 0 ? tmp * 0x8000 : tmp * 0x7fff, true);\n }\n\n return dataview;\n }\n\n /**\n * Writes a string to a DataView at the specified offset.\n *\n * @internal\n */\n private _writeString(dataview: DataView, offset: number, header: string): void {\n for (let i = 0; i < header.length; i++) {\n dataview.setUint8(offset + i, header.charCodeAt(i));\n }\n }\n\n /**\n * Converts an AudioBuffer to a Float32Array.\n *\n * @internal\n */\n private _interleave(input: AudioBuffer): Float32Array {\n if (input.numberOfChannels === 1) {\n // No need to interleave channels, just return single channel data to save performance and memory\n return input.getChannelData(0);\n }\n const channels = [];\n for (let i = 0; i < input.numberOfChannels; i++) {\n channels.push(input.getChannelData(i));\n }\n const length = channels.reduce((prev, channelData) => prev + channelData.length, 0);\n const result = new Float32Array(length);\n\n let index = 0;\n let inputIndex = 0;\n\n // for 2 channels its like: [L[0], R[0], L[1], R[1], ... , L[n], R[n]]\n while (index < length) {\n channels.forEach((channelData) => {\n result[index++] = channelData[inputIndex];\n });\n\n inputIndex++;\n }\n\n return result;\n }\n\n /**\n * Creates an HTMLAudioElement whose source is the specified Blob.\n *\n * @internal\n */\n private _renderAudioElement(blob: Blob): HTMLAudioElement {\n const audio = document.createElement('audio');\n\n audio.controls = true;\n audio.src = this._renderURL(blob);\n\n return audio;\n }\n\n /**\n * Creates an Object URL for the specified Blob.\n *\n * @internal\n */\n private _renderURL(blob: Blob): string {\n return (window.URL || window.webkitURL).createObjectURL(blob);\n }\n}\n"],"names":["__webpack_require__","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","Crunker","sampleRate","this","_context","_createContext","_sampleRate","window","AudioContext","webkitAudioContext","mozAudioContext","async","filepaths","Promise","all","map","filepath","buffer","File","Blob","arrayBuffer","fetch","then","response","headers","has","includes","console","warn","split","decodeAudioData","buffers","output","createBuffer","_maxNumberOfChannels","_maxDuration","forEach","channelNumber","numberOfChannels","outputData","getChannelData","bufferData","i","length","set","_totalLength","offset","padStart","seconds","Error","updatedBuffer","Math","ceil","channelData","subarray","start","end","fadeIn","fadeOut","round","newBuffer","channel","inputData","source","createBufferSource","connect","destination","type","recorded","_interleave","dataview","_writeHeaders","audioBlob","blob","url","_renderURL","element","_renderAudioElement","filename","a","document","createElement","style","display","href","download","click","callback","_isSupported","undefined","close","max","duration","reduce","b","numOfChannels","sampleSize","bitDepth","chunkDataSize","chunkTotalSize","ArrayBuffer","view","DataView","_writeString","setUint32","setUint16","_floatTo16BitPCM","fileHeaderSize","tmp","min","setInt16","header","setUint8","charCodeAt","input","channels","push","prev","result","Float32Array","index","inputIndex","audio","controls","src","URL","webkitURL","createObjectURL"],"sourceRoot":""} \ No newline at end of file diff --git a/system/tts_generator/favicon.ico b/system/tts_generator/favicon.ico new file mode 100644 index 0000000..1e0d7ff Binary files /dev/null and b/system/tts_generator/favicon.ico differ diff --git a/system/tts_generator/howler.min.js b/system/tts_generator/howler.min.js new file mode 100644 index 0000000..40f9a2f --- /dev/null +++ b/system/tts_generator/howler.min.js @@ -0,0 +1,4 @@ +/*! howler.js v2.2.4 | (c) 2013-2020, James Simpson of GoldFire Studios | MIT License | howlerjs.com */ +!function(){"use strict";var e=function(){this.init()};e.prototype={init:function(){var e=this||n;return e._counter=1e3,e._html5AudioPool=[],e.html5PoolSize=10,e._codecs={},e._howls=[],e._muted=!1,e._volume=1,e._canPlayEvent="canplaythrough",e._navigator="undefined"!=typeof window&&window.navigator?window.navigator:null,e.masterGain=null,e.noAudio=!1,e.usingWebAudio=!0,e.autoSuspend=!0,e.ctx=null,e.autoUnlock=!0,e._setup(),e},volume:function(e){var o=this||n;if(e=parseFloat(e),o.ctx||_(),void 0!==e&&e>=0&&e<=1){if(o._volume=e,o._muted)return o;o.usingWebAudio&&o.masterGain.gain.setValueAtTime(e,n.ctx.currentTime);for(var t=0;t=0;o--)e._howls[o].unload();return e.usingWebAudio&&e.ctx&&void 0!==e.ctx.close&&(e.ctx.close(),e.ctx=null,_()),e},codecs:function(e){return(this||n)._codecs[e.replace(/^x-/,"")]},_setup:function(){var e=this||n;if(e.state=e.ctx?e.ctx.state||"suspended":"suspended",e._autoSuspend(),!e.usingWebAudio)if("undefined"!=typeof Audio)try{var o=new Audio;void 0===o.oncanplaythrough&&(e._canPlayEvent="canplay")}catch(n){e.noAudio=!0}else e.noAudio=!0;try{var o=new Audio;o.muted&&(e.noAudio=!0)}catch(e){}return e.noAudio||e._setupCodecs(),e},_setupCodecs:function(){var e=this||n,o=null;try{o="undefined"!=typeof Audio?new Audio:null}catch(n){return e}if(!o||"function"!=typeof o.canPlayType)return e;var t=o.canPlayType("audio/mpeg;").replace(/^no$/,""),r=e._navigator?e._navigator.userAgent:"",a=r.match(/OPR\/(\d+)/g),u=a&&parseInt(a[0].split("/")[1],10)<33,d=-1!==r.indexOf("Safari")&&-1===r.indexOf("Chrome"),i=r.match(/Version\/(.*?) /),_=d&&i&&parseInt(i[1],10)<15;return e._codecs={mp3:!(u||!t&&!o.canPlayType("audio/mp3;").replace(/^no$/,"")),mpeg:!!t,opus:!!o.canPlayType('audio/ogg; codecs="opus"').replace(/^no$/,""),ogg:!!o.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),oga:!!o.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),wav:!!(o.canPlayType('audio/wav; codecs="1"')||o.canPlayType("audio/wav")).replace(/^no$/,""),aac:!!o.canPlayType("audio/aac;").replace(/^no$/,""),caf:!!o.canPlayType("audio/x-caf;").replace(/^no$/,""),m4a:!!(o.canPlayType("audio/x-m4a;")||o.canPlayType("audio/m4a;")||o.canPlayType("audio/aac;")).replace(/^no$/,""),m4b:!!(o.canPlayType("audio/x-m4b;")||o.canPlayType("audio/m4b;")||o.canPlayType("audio/aac;")).replace(/^no$/,""),mp4:!!(o.canPlayType("audio/x-mp4;")||o.canPlayType("audio/mp4;")||o.canPlayType("audio/aac;")).replace(/^no$/,""),weba:!(_||!o.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/,"")),webm:!(_||!o.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/,"")),dolby:!!o.canPlayType('audio/mp4; codecs="ec-3"').replace(/^no$/,""),flac:!!(o.canPlayType("audio/x-flac;")||o.canPlayType("audio/flac;")).replace(/^no$/,"")},e},_unlockAudio:function(){var e=this||n;if(!e._audioUnlocked&&e.ctx){e._audioUnlocked=!1,e.autoUnlock=!1,e._mobileUnloaded||44100===e.ctx.sampleRate||(e._mobileUnloaded=!0,e.unload()),e._scratchBuffer=e.ctx.createBuffer(1,1,22050);var o=function(n){for(;e._html5AudioPool.length0?d._seek:t._sprite[e][0]/1e3),s=Math.max(0,(t._sprite[e][0]+t._sprite[e][1])/1e3-_),l=1e3*s/Math.abs(d._rate),c=t._sprite[e][0]/1e3,f=(t._sprite[e][0]+t._sprite[e][1])/1e3;d._sprite=e,d._ended=!1;var p=function(){d._paused=!1,d._seek=_,d._start=c,d._stop=f,d._loop=!(!d._loop&&!t._sprite[e][2])};if(_>=f)return void t._ended(d);var m=d._node;if(t._webAudio){var v=function(){t._playLock=!1,p(),t._refreshBuffer(d);var e=d._muted||t._muted?0:d._volume;m.gain.setValueAtTime(e,n.ctx.currentTime),d._playStart=n.ctx.currentTime,void 0===m.bufferSource.start?d._loop?m.bufferSource.noteGrainOn(0,_,86400):m.bufferSource.noteGrainOn(0,_,s):d._loop?m.bufferSource.start(0,_,86400):m.bufferSource.start(0,_,s),l!==1/0&&(t._endTimers[d._id]=setTimeout(t._ended.bind(t,d),l)),o||setTimeout(function(){t._emit("play",d._id),t._loadQueue()},0)};"running"===n.state&&"interrupted"!==n.ctx.state?v():(t._playLock=!0,t.once("resume",v),t._clearTimer(d._id))}else{var h=function(){m.currentTime=_,m.muted=d._muted||t._muted||n._muted||m.muted,m.volume=d._volume*n.volume(),m.playbackRate=d._rate;try{var r=m.play();if(r&&"undefined"!=typeof Promise&&(r instanceof Promise||"function"==typeof r.then)?(t._playLock=!0,p(),r.then(function(){t._playLock=!1,m._unlocked=!0,o?t._loadQueue():t._emit("play",d._id)}).catch(function(){t._playLock=!1,t._emit("playerror",d._id,"Playback was unable to start. This is most commonly an issue on mobile devices and Chrome where playback was not within a user interaction."),d._ended=!0,d._paused=!0})):o||(t._playLock=!1,p(),t._emit("play",d._id)),m.playbackRate=d._rate,m.paused)return void t._emit("playerror",d._id,"Playback was unable to start. This is most commonly an issue on mobile devices and Chrome where playback was not within a user interaction.");"__default"!==e||d._loop?t._endTimers[d._id]=setTimeout(t._ended.bind(t,d),l):(t._endTimers[d._id]=function(){t._ended(d),m.removeEventListener("ended",t._endTimers[d._id],!1)},m.addEventListener("ended",t._endTimers[d._id],!1))}catch(e){t._emit("playerror",d._id,e)}};"data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA"===m.src&&(m.src=t._src,m.load());var y=window&&window.ejecta||!m.readyState&&n._navigator.isCocoonJS;if(m.readyState>=3||y)h();else{t._playLock=!0,t._state="loading";var g=function(){t._state="loaded",h(),m.removeEventListener(n._canPlayEvent,g,!1)};m.addEventListener(n._canPlayEvent,g,!1),t._clearTimer(d._id)}}return d._id},pause:function(e){var n=this;if("loaded"!==n._state||n._playLock)return n._queue.push({event:"pause",action:function(){n.pause(e)}}),n;for(var o=n._getSoundIds(e),t=0;t=0?o=parseInt(r[0],10):e=parseFloat(r[0])}else r.length>=2&&(e=parseFloat(r[0]),o=parseInt(r[1],10));var a;if(!(void 0!==e&&e>=0&&e<=1))return a=o?t._soundById(o):t._sounds[0],a?a._volume:0;if("loaded"!==t._state||t._playLock)return t._queue.push({event:"volume",action:function(){t.volume.apply(t,r)}}),t;void 0===o&&(t._volume=e),o=t._getSoundIds(o);for(var u=0;u0?t/_:t),l=Date.now();e._fadeTo=o,e._interval=setInterval(function(){var r=(Date.now()-l)/t;l=Date.now(),d+=i*r,d=Math.round(100*d)/100,d=i<0?Math.max(o,d):Math.min(o,d),u._webAudio?e._volume=d:u.volume(d,e._id,!0),a&&(u._volume=d),(on&&d>=o)&&(clearInterval(e._interval),e._interval=null,e._fadeTo=null,u.volume(o,e._id),u._emit("fade",e._id))},s)},_stopFade:function(e){var o=this,t=o._soundById(e);return t&&t._interval&&(o._webAudio&&t._node.gain.cancelScheduledValues(n.ctx.currentTime),clearInterval(t._interval),t._interval=null,o.volume(t._fadeTo,e),t._fadeTo=null,o._emit("fade",e)),o},loop:function(){var e,n,o,t=this,r=arguments;if(0===r.length)return t._loop;if(1===r.length){if("boolean"!=typeof r[0])return!!(o=t._soundById(parseInt(r[0],10)))&&o._loop;e=r[0],t._loop=e}else 2===r.length&&(e=r[0],n=parseInt(r[1],10));for(var a=t._getSoundIds(n),u=0;u=0?o=parseInt(r[0],10):e=parseFloat(r[0])}else 2===r.length&&(e=parseFloat(r[0]),o=parseInt(r[1],10));var d;if("number"!=typeof e)return d=t._soundById(o),d?d._rate:t._rate;if("loaded"!==t._state||t._playLock)return t._queue.push({event:"rate",action:function(){t.rate.apply(t,r)}}),t;void 0===o&&(t._rate=e),o=t._getSoundIds(o);for(var i=0;i=0?o=parseInt(r[0],10):t._sounds.length&&(o=t._sounds[0]._id,e=parseFloat(r[0]))}else 2===r.length&&(e=parseFloat(r[0]),o=parseInt(r[1],10));if(void 0===o)return 0;if("number"==typeof e&&("loaded"!==t._state||t._playLock))return t._queue.push({event:"seek",action:function(){t.seek.apply(t,r)}}),t;var d=t._soundById(o);if(d){if(!("number"==typeof e&&e>=0)){if(t._webAudio){var i=t.playing(o)?n.ctx.currentTime-d._playStart:0,_=d._rateSeek?d._rateSeek-d._seek:0;return d._seek+(_+i*Math.abs(d._rate))}return d._node.currentTime}var s=t.playing(o);s&&t.pause(o,!0),d._seek=e,d._ended=!1,t._clearTimer(o),t._webAudio||!d._node||isNaN(d._node.duration)||(d._node.currentTime=e);var l=function(){s&&t.play(o,!0),t._emit("seek",o)};if(s&&!t._webAudio){var c=function(){t._playLock?setTimeout(c,0):l()};setTimeout(c,0)}else l()}return t},playing:function(e){var n=this;if("number"==typeof e){var o=n._soundById(e);return!!o&&!o._paused}for(var t=0;t=0&&n._howls.splice(a,1);var u=!0;for(t=0;t=0){u=!1;break}return r&&u&&delete r[e._src],n.noAudio=!1,e._state="unloaded",e._sounds=[],e=null,null},on:function(e,n,o,t){var r=this,a=r["_on"+e];return"function"==typeof n&&a.push(t?{id:o,fn:n,once:t}:{id:o,fn:n}),r},off:function(e,n,o){var t=this,r=t["_on"+e],a=0;if("number"==typeof n&&(o=n,n=null),n||o)for(a=0;a=0;a--)r[a].id&&r[a].id!==n&&"load"!==e||(setTimeout(function(e){e.call(this,n,o)}.bind(t,r[a].fn),0),r[a].once&&t.off(e,r[a].fn,r[a].id));return t._loadQueue(e),t},_loadQueue:function(e){var n=this;if(n._queue.length>0){var o=n._queue[0];o.event===e&&(n._queue.shift(),n._loadQueue()),e||o.action()}return n},_ended:function(e){var o=this,t=e._sprite;if(!o._webAudio&&e._node&&!e._node.paused&&!e._node.ended&&e._node.currentTime=0;t--){if(o<=n)return;e._sounds[t]._ended&&(e._webAudio&&e._sounds[t]._node&&e._sounds[t]._node.disconnect(0),e._sounds.splice(t,1),o--)}}},_getSoundIds:function(e){var n=this;if(void 0===e){for(var o=[],t=0;t=0;if(!e.bufferSource)return o;if(n._scratchBuffer&&e.bufferSource&&(e.bufferSource.onended=null,e.bufferSource.disconnect(0),t))try{e.bufferSource.buffer=n._scratchBuffer}catch(e){}return e.bufferSource=null,o},_clearSound:function(e){/MSIE |Trident\//.test(n._navigator&&n._navigator.userAgent)||(e.src="data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA")}};var t=function(e){this._parent=e,this.init()};t.prototype={init:function(){var e=this,o=e._parent;return e._muted=o._muted,e._loop=o._loop,e._volume=o._volume,e._rate=o._rate,e._seek=0,e._paused=!0,e._ended=!0,e._sprite="__default",e._id=++n._counter,o._sounds.push(e),e.create(),e},create:function(){var e=this,o=e._parent,t=n._muted||e._muted||e._parent._muted?0:e._volume;return o._webAudio?(e._node=void 0===n.ctx.createGain?n.ctx.createGainNode():n.ctx.createGain(),e._node.gain.setValueAtTime(t,n.ctx.currentTime),e._node.paused=!0,e._node.connect(n.masterGain)):n.noAudio||(e._node=n._obtainHtml5Audio(),e._errorFn=e._errorListener.bind(e),e._node.addEventListener("error",e._errorFn,!1),e._loadFn=e._loadListener.bind(e),e._node.addEventListener(n._canPlayEvent,e._loadFn,!1),e._endFn=e._endListener.bind(e),e._node.addEventListener("ended",e._endFn,!1),e._node.src=o._src,e._node.preload=!0===o._preload?"auto":o._preload,e._node.volume=t*n.volume(),e._node.load()),e},reset:function(){var e=this,o=e._parent;return e._muted=o._muted,e._loop=o._loop,e._volume=o._volume,e._rate=o._rate,e._seek=0,e._rateSeek=0,e._paused=!0,e._ended=!0,e._sprite="__default",e._id=++n._counter,e},_errorListener:function(){var e=this;e._parent._emit("loaderror",e._id,e._node.error?e._node.error.code:0),e._node.removeEventListener("error",e._errorFn,!1)},_loadListener:function(){var e=this,o=e._parent;o._duration=Math.ceil(10*e._node.duration)/10,0===Object.keys(o._sprite).length&&(o._sprite={__default:[0,1e3*o._duration]}),"loaded"!==o._state&&(o._state="loaded",o._emit("load"),o._loadQueue()),e._node.removeEventListener(n._canPlayEvent,e._loadFn,!1)},_endListener:function(){var e=this,n=e._parent;n._duration===1/0&&(n._duration=Math.ceil(10*e._node.duration)/10,n._sprite.__default[1]===1/0&&(n._sprite.__default[1]=1e3*n._duration),n._ended(e)),e._node.removeEventListener("ended",e._endFn,!1)}};var r={},a=function(e){var n=e._src;if(r[n])return e._duration=r[n].duration,void i(e);if(/^data:[^;]+;base64,/.test(n)){for(var o=atob(n.split(",")[1]),t=new Uint8Array(o.length),a=0;a0?(r[o._src]=e,i(o,e)):t()};"undefined"!=typeof Promise&&1===n.ctx.decodeAudioData.length?n.ctx.decodeAudioData(e).then(a).catch(t):n.ctx.decodeAudioData(e,a,t)},i=function(e,n){n&&!e._duration&&(e._duration=n.duration),0===Object.keys(e._sprite).length&&(e._sprite={__default:[0,1e3*e._duration]}),"loaded"!==e._state&&(e._state="loaded",e._emit("load"),e._loadQueue())},_=function(){if(n.usingWebAudio){try{"undefined"!=typeof AudioContext?n.ctx=new AudioContext:"undefined"!=typeof webkitAudioContext?n.ctx=new webkitAudioContext:n.usingWebAudio=!1}catch(e){n.usingWebAudio=!1}n.ctx||(n.usingWebAudio=!1);var e=/iP(hone|od|ad)/.test(n._navigator&&n._navigator.platform),o=n._navigator&&n._navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/),t=o?parseInt(o[1],10):null;if(e&&t&&t<9){var r=/safari/.test(n._navigator&&n._navigator.userAgent.toLowerCase());n._navigator&&!r&&(n.usingWebAudio=!1)}n.usingWebAudio&&(n.masterGain=void 0===n.ctx.createGain?n.ctx.createGainNode():n.ctx.createGain(),n.masterGain.gain.setValueAtTime(n._muted?0:n._volume,n.ctx.currentTime),n.masterGain.connect(n.ctx.destination)),n._setup()}};"function"==typeof define&&define.amd&&define([],function(){return{Howler:n,Howl:o}}),"undefined"!=typeof exports&&(exports.Howler=n,exports.Howl=o),"undefined"!=typeof global?(global.HowlerGlobal=e,global.Howler=n,global.Howl=o,global.Sound=t):"undefined"!=typeof window&&(window.HowlerGlobal=e,window.Howler=n,window.Howl=o,window.Sound=t)}(); +/*! Spatial Plugin */ +!function(){"use strict";HowlerGlobal.prototype._pos=[0,0,0],HowlerGlobal.prototype._orientation=[0,0,-1,0,1,0],HowlerGlobal.prototype.stereo=function(e){var n=this;if(!n.ctx||!n.ctx.listener)return n;for(var t=n._howls.length-1;t>=0;t--)n._howls[t].stereo(e);return n},HowlerGlobal.prototype.pos=function(e,n,t){var r=this;return r.ctx&&r.ctx.listener?(n="number"!=typeof n?r._pos[1]:n,t="number"!=typeof t?r._pos[2]:t,"number"!=typeof e?r._pos:(r._pos=[e,n,t],void 0!==r.ctx.listener.positionX?(r.ctx.listener.positionX.setTargetAtTime(r._pos[0],Howler.ctx.currentTime,.1),r.ctx.listener.positionY.setTargetAtTime(r._pos[1],Howler.ctx.currentTime,.1),r.ctx.listener.positionZ.setTargetAtTime(r._pos[2],Howler.ctx.currentTime,.1)):r.ctx.listener.setPosition(r._pos[0],r._pos[1],r._pos[2]),r)):r},HowlerGlobal.prototype.orientation=function(e,n,t,r,o,i){var a=this;if(!a.ctx||!a.ctx.listener)return a;var p=a._orientation;return n="number"!=typeof n?p[1]:n,t="number"!=typeof t?p[2]:t,r="number"!=typeof r?p[3]:r,o="number"!=typeof o?p[4]:o,i="number"!=typeof i?p[5]:i,"number"!=typeof e?p:(a._orientation=[e,n,t,r,o,i],void 0!==a.ctx.listener.forwardX?(a.ctx.listener.forwardX.setTargetAtTime(e,Howler.ctx.currentTime,.1),a.ctx.listener.forwardY.setTargetAtTime(n,Howler.ctx.currentTime,.1),a.ctx.listener.forwardZ.setTargetAtTime(t,Howler.ctx.currentTime,.1),a.ctx.listener.upX.setTargetAtTime(r,Howler.ctx.currentTime,.1),a.ctx.listener.upY.setTargetAtTime(o,Howler.ctx.currentTime,.1),a.ctx.listener.upZ.setTargetAtTime(i,Howler.ctx.currentTime,.1)):a.ctx.listener.setOrientation(e,n,t,r,o,i),a)},Howl.prototype.init=function(e){return function(n){var t=this;return t._orientation=n.orientation||[1,0,0],t._stereo=n.stereo||null,t._pos=n.pos||null,t._pannerAttr={coneInnerAngle:void 0!==n.coneInnerAngle?n.coneInnerAngle:360,coneOuterAngle:void 0!==n.coneOuterAngle?n.coneOuterAngle:360,coneOuterGain:void 0!==n.coneOuterGain?n.coneOuterGain:0,distanceModel:void 0!==n.distanceModel?n.distanceModel:"inverse",maxDistance:void 0!==n.maxDistance?n.maxDistance:1e4,panningModel:void 0!==n.panningModel?n.panningModel:"HRTF",refDistance:void 0!==n.refDistance?n.refDistance:1,rolloffFactor:void 0!==n.rolloffFactor?n.rolloffFactor:1},t._onstereo=n.onstereo?[{fn:n.onstereo}]:[],t._onpos=n.onpos?[{fn:n.onpos}]:[],t._onorientation=n.onorientation?[{fn:n.onorientation}]:[],e.call(this,n)}}(Howl.prototype.init),Howl.prototype.stereo=function(n,t){var r=this;if(!r._webAudio)return r;if("loaded"!==r._state)return r._queue.push({event:"stereo",action:function(){r.stereo(n,t)}}),r;var o=void 0===Howler.ctx.createStereoPanner?"spatial":"stereo";if(void 0===t){if("number"!=typeof n)return r._stereo;r._stereo=n,r._pos=[n,0,0]}for(var i=r._getSoundIds(t),a=0;a + + + + + + + AllTalk TTS Generator + + + + + + +

AllTalk TTS Generator

+ +
+
+ + +
+ +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ + 2 +
+
+ +
+ +
+ + 0.8 +
+
+
+ +
+ + + + + + + + +

Word Count: 0

+

         Idle

+
+ + + +
+ +
+

Generated TTS List

+ + + + + + + + + + + + + +
+ + Page 1 of X + + + +
+
+ +
+
+ + Page 1 of X + + + +
+
+ + + + + + + + + + \ No newline at end of file diff --git a/system/tts_srt/tts_srt.py b/system/tts_srt/tts_srt.py new file mode 100644 index 0000000..f2ba80f --- /dev/null +++ b/system/tts_srt/tts_srt.py @@ -0,0 +1,77 @@ +import json +import argparse +import soundfile as sf +from pathlib import Path + +parser = argparse.ArgumentParser(description="Compare TTS output with the original text using detailed comparison.") +parser.add_argument("--ttslistpath", help="Path to the ttsList.json file") +parser.add_argument("--wavfilespath", help="Path to the wav outputs folder") +args = parser.parse_args() + +if not args.ttslistpath: + parser.error("[AllTalk TTSSRT] Please specify the path to the ttslist.json file using the --ttslistpath argument.") +if not args.wavfilespath: + parser.error("[AllTalk TTSSRT] Please specify the path to the wavs output folder file using the --wavfilespath argument.") + +json_file_path = Path(args.ttslistpath).resolve() +audio_files_base_path = Path(args.wavfilespath).resolve() + +print(f"[AllTalk TTSSRT]") +print("[AllTalk TTSSRT] \033[92mStarting SRT Subtitle generation:\033[0m") +print("[AllTalk TTSSRT]") +print("[AllTalk TTSSRT] \033[94mJSON file :\033[92m", json_file_path, "\033[0m") +print("[AllTalk TTSSRT] \033[94mWAV files :\033[92m", audio_files_base_path, "\033[0m") +print("[AllTalk TTSSRT]") + +# Function to convert time in seconds to SRT time format +def format_srt_time(seconds): + hours = int(seconds // 3600) + minutes = int((seconds % 3600) // 60) + seconds = seconds % 60 + milliseconds = int((seconds - int(seconds)) * 1000) + return f"{hours:02}:{minutes:02}:{int(seconds):02},{milliseconds:03}" + +# Load the JSON data +with open(json_file_path, 'r') as json_file: + data = json.load(json_file) + +# Initialize start time and SRT lines +start_time = 0.0 +srt_lines = [] + +# Loop through the JSON data to build SRT entries +for entry in data: + file_name = Path(entry['fileUrl']).name # Get the file name from the URL + file_path = audio_files_base_path / file_name + print(f"[AllTalk TTSSRT] Processing file: {file_name}") + + if not file_path.exists(): + print(f"[AllTalk TTSSRT] Audio file does not exist: {file_path}") + continue + + try: + # Use soundfile to read the duration of the WAV file + with sf.SoundFile(str(file_path), 'r') as f: + duration = len(f) / f.samplerate + end_time = start_time + duration + + # Format the SRT entry + start_timestamp = format_srt_time(start_time) + end_timestamp = format_srt_time(end_time) + srt_lines.append(f"{len(srt_lines)+1}\n{start_timestamp} --> {end_timestamp}\n{entry['text']}\n") + + # Update the start time for the next entry + start_time = end_time + except RuntimeError as e: + print(f"[AllTalk TTSSRT] Error reading {file_path}: {e}") + +# Join all SRT lines into the final SRT content +srt_content = "\n".join(srt_lines) + +# Save the SRT content to a file +srt_file_path = audio_files_base_path / "subtitles.srt" +with open(srt_file_path, "w") as srt_file: + srt_file.write(srt_content) + +print("[AllTalk TTSSRT]") +print(f"[AllTalk TTSSRT] \033[94mSRT file created at \033[0m{srt_file_path}") diff --git a/system/word_addin/1995fcd6d39cade7d871.js b/system/word_addin/1995fcd6d39cade7d871.js new file mode 100644 index 0000000..b18cd4a --- /dev/null +++ b/system/word_addin/1995fcd6d39cade7d871.js @@ -0,0 +1,2 @@ +/*! For license information please see 1995fcd6d39cade7d871.js.LICENSE.txt */ +function _typeof(t){return _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},_typeof(t)}function _regeneratorRuntime(){"use strict";_regeneratorRuntime=function(){return e};var t,e={},r=Object.prototype,n=r.hasOwnProperty,o=Object.defineProperty||function(t,e,r){t[e]=r.value},i="function"==typeof Symbol?Symbol:{},a=i.iterator||"@@iterator",c=i.asyncIterator||"@@asyncIterator",u=i.toStringTag||"@@toStringTag";function l(t,e,r){return Object.defineProperty(t,e,{value:r,enumerable:!0,configurable:!0,writable:!0}),t[e]}try{l({},"")}catch(t){l=function(t,e,r){return t[e]=r}}function f(t,e,r,n){var i=e&&e.prototype instanceof m?e:m,a=Object.create(i.prototype),c=new G(n||[]);return o(a,"_invoke",{value:S(t,r,c)}),a}function s(t,e,r){try{return{type:"normal",arg:t.call(e,r)}}catch(t){return{type:"throw",arg:t}}}e.wrap=f;var h="suspendedStart",p="suspendedYield",y="executing",d="completed",v={};function m(){}function g(){}function w(){}var x={};l(x,a,(function(){return this}));var E=Object.getPrototypeOf,b=E&&E(E(B([])));b&&b!==r&&n.call(b,a)&&(x=b);var T=w.prototype=m.prototype=Object.create(x);function _(t){["next","throw","return"].forEach((function(e){l(t,e,(function(t){return this._invoke(e,t)}))}))}function L(t,e){function r(o,i,a,c){var u=s(t[o],t,i);if("throw"!==u.type){var l=u.arg,f=l.value;return f&&"object"==_typeof(f)&&n.call(f,"__await")?e.resolve(f.__await).then((function(t){r("next",t,a,c)}),(function(t){r("throw",t,a,c)})):e.resolve(f).then((function(t){l.value=t,a(l)}),(function(t){return r("throw",t,a,c)}))}c(u.arg)}var i;o(this,"_invoke",{value:function(t,n){function o(){return new e((function(e,o){r(t,n,e,o)}))}return i=i?i.then(o,o):o()}})}function S(e,r,n){var o=h;return function(i,a){if(o===y)throw new Error("Generator is already running");if(o===d){if("throw"===i)throw a;return{value:t,done:!0}}for(n.method=i,n.arg=a;;){var c=n.delegate;if(c){var u=I(c,n);if(u){if(u===v)continue;return u}}if("next"===n.method)n.sent=n._sent=n.arg;else if("throw"===n.method){if(o===h)throw o=d,n.arg;n.dispatchException(n.arg)}else"return"===n.method&&n.abrupt("return",n.arg);o=y;var l=s(e,r,n);if("normal"===l.type){if(o=n.done?d:p,l.arg===v)continue;return{value:l.arg,done:n.done}}"throw"===l.type&&(o=d,n.method="throw",n.arg=l.arg)}}}function I(e,r){var n=r.method,o=e.iterator[n];if(o===t)return r.delegate=null,"throw"===n&&e.iterator.return&&(r.method="return",r.arg=t,I(e,r),"throw"===r.method)||"return"!==n&&(r.method="throw",r.arg=new TypeError("The iterator does not provide a '"+n+"' method")),v;var i=s(o,e.iterator,r.arg);if("throw"===i.type)return r.method="throw",r.arg=i.arg,r.delegate=null,v;var a=i.arg;return a?a.done?(r[e.resultName]=a.value,r.next=e.nextLoc,"return"!==r.method&&(r.method="next",r.arg=t),r.delegate=null,v):a:(r.method="throw",r.arg=new TypeError("iterator result is not an object"),r.delegate=null,v)}function O(t){var e={tryLoc:t[0]};1 in t&&(e.catchLoc=t[1]),2 in t&&(e.finallyLoc=t[2],e.afterLoc=t[3]),this.tryEntries.push(e)}function j(t){var e=t.completion||{};e.type="normal",delete e.arg,t.completion=e}function G(t){this.tryEntries=[{tryLoc:"root"}],t.forEach(O,this),this.reset(!0)}function B(e){if(e||""===e){var r=e[a];if(r)return r.call(e);if("function"==typeof e.next)return e;if(!isNaN(e.length)){var o=-1,i=function r(){for(;++o=0;--i){var a=this.tryEntries[i],c=a.completion;if("root"===a.tryLoc)return o("end");if(a.tryLoc<=this.prev){var u=n.call(a,"catchLoc"),l=n.call(a,"finallyLoc");if(u&&l){if(this.prev=0;--r){var o=this.tryEntries[r];if(o.tryLoc<=this.prev&&n.call(o,"finallyLoc")&&this.prev=0;--e){var r=this.tryEntries[e];if(r.finallyLoc===t)return this.complete(r.completion,r.afterLoc),j(r),v}},catch:function(t){for(var e=this.tryEntries.length-1;e>=0;--e){var r=this.tryEntries[e];if(r.tryLoc===t){var n=r.completion;if("throw"===n.type){var o=n.arg;j(r)}return o}}throw new Error("illegal catch attempt")},delegateYield:function(e,r,n){return this.delegate={iterator:B(e),resultName:r,nextLoc:n},"next"===this.method&&(this.arg=t),v}},e}function asyncGeneratorStep(t,e,r,n,o,i,a){try{var c=t[i](a),u=c.value}catch(t){return void r(t)}c.done?e(u):Promise.resolve(u).then(n,o)}function _asyncToGenerator(t){return function(){var e=this,r=arguments;return new Promise((function(n,o){var i=t.apply(e,r);function a(t){asyncGeneratorStep(i,n,o,a,c,"next",t)}function c(t){asyncGeneratorStep(i,n,o,a,c,"throw",t)}a(void 0)}))}}Office.onReady((function(t){t.host===Office.HostType.Word&&(loadVoices(),document.getElementById("sendTextButton").onclick=sendTextToTTS)}));var audio=document.getElementById("audioSource"),volumeControl=document.getElementById("volumeControl");function loadVoices(){var t=document.getElementById("serverIP").value,e=document.getElementById("portNumber").value,r="http://".concat(t,":").concat(e,"/api/voices");fetch(r).then((function(t){return t.json()})).then((function(t){var e=document.getElementById("voice");t.voices.forEach((function(t){var r=document.createElement("option");r.value=t,r.textContent=t,e.appendChild(r)}))})).catch((function(t){return console.error("Error fetching voices:",t)}))}function sendTextToTTS(){return _sendTextToTTS.apply(this,arguments)}function _sendTextToTTS(){return _sendTextToTTS=_asyncToGenerator(_regeneratorRuntime().mark((function t(){return _regeneratorRuntime().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return t.next=2,Word.run(function(){var t=_asyncToGenerator(_regeneratorRuntime().mark((function t(e){var r,n,o,i,a,c,u,l,f,s;return _regeneratorRuntime().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return(r=e.document.getSelection()).load("text"),t.next=4,e.sync();case 4:n=r.text,o=document.getElementById("voice"),i=o.value,a=document.getElementById("language"),c=a.value,u=document.getElementById("serverIP").value,l=document.getElementById("portNumber").value,f=document.getElementById("audioSource"),s="text=".concat(n,"&voice=").concat(i,"&language=").concat(c,"&output_file=").concat("demo_output.wav","&streaming=").concat("true"),f.src="http://".concat(u,":").concat(l,"/tts-demo-request?").concat(s),f.load();case 17:case"end":return t.stop()}}),t)})));return function(e){return t.apply(this,arguments)}}()).catch((function(t){console.error("Error:",t)}));case 2:case"end":return t.stop()}}),t)}))),_sendTextToTTS.apply(this,arguments)}volumeControl.addEventListener("input",(function(){audio.volume=volumeControl.value})); \ No newline at end of file diff --git a/system/word_addin/1995fcd6d39cade7d871.js.LICENSE.txt b/system/word_addin/1995fcd6d39cade7d871.js.LICENSE.txt new file mode 100644 index 0000000..ae386fb --- /dev/null +++ b/system/word_addin/1995fcd6d39cade7d871.js.LICENSE.txt @@ -0,0 +1 @@ +/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ diff --git a/system/word_addin/1fda685b81e1123773f6.css b/system/word_addin/1fda685b81e1123773f6.css new file mode 100644 index 0000000..5f78c16 --- /dev/null +++ b/system/word_addin/1fda685b81e1123773f6.css @@ -0,0 +1,80 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. + * See LICENSE in the project root for license information. + */ + + html, + body { + width: 100%; + height: 100%; + margin: 0; + padding: 0; + } + + ul { + margin: 0; + padding: 0; + } + + .ms-welcome__header { + padding: 20px; + padding-bottom: 30px; + padding-top: 100px; + display: -webkit-flex; + display: flex; + -webkit-flex-direction: column; + flex-direction: column; + align-items: center; + } + + .ms-welcome__main { + display: -webkit-flex; + display: flex; + -webkit-flex-direction: column; + flex-direction: column; + -webkit-flex-wrap: nowrap; + flex-wrap: nowrap; + -webkit-align-items: center; + align-items: center; + -webkit-flex: 1 0 0; + flex: 1 0 0; + padding: 10px 20px; + } + + .ms-welcome__main > h2 { + width: 100%; + text-align: center; + } + + .ms-welcome__features { + list-style-type: none; + margin-top: 20px; + } + + .ms-welcome__features.ms-List .ms-ListItem { + padding-bottom: 20px; + display: -webkit-flex; + display: flex; + } + + .ms-welcome__features.ms-List .ms-ListItem > .ms-Icon { + margin-right: 10px; + } + + .ms-welcome__action.ms-Button--hero { + margin-top: 30px; + } + +.ms-Button.ms-Button--hero .ms-Button-label { + color: #0078d7; +} + +.ms-Button.ms-Button--hero:hover .ms-Button-label, +.ms-Button.ms-Button--hero:focus .ms-Button-label{ + color: #005a9e; + cursor: pointer; +} + +b { + font-weight: bold; +} \ No newline at end of file diff --git a/system/word_addin/AllTalk Word Add-in.pdf b/system/word_addin/AllTalk Word Add-in.pdf new file mode 100644 index 0000000..3f117a2 Binary files /dev/null and b/system/word_addin/AllTalk Word Add-in.pdf differ diff --git a/system/word_addin/assets/icon-128.png b/system/word_addin/assets/icon-128.png new file mode 100644 index 0000000..94e2021 Binary files /dev/null and b/system/word_addin/assets/icon-128.png differ diff --git a/system/word_addin/assets/icon-16.png b/system/word_addin/assets/icon-16.png new file mode 100644 index 0000000..4ea5009 Binary files /dev/null and b/system/word_addin/assets/icon-16.png differ diff --git a/system/word_addin/assets/icon-32.png b/system/word_addin/assets/icon-32.png new file mode 100644 index 0000000..f3b1580 Binary files /dev/null and b/system/word_addin/assets/icon-32.png differ diff --git a/system/word_addin/assets/icon-64.png b/system/word_addin/assets/icon-64.png new file mode 100644 index 0000000..b01cf27 Binary files /dev/null and b/system/word_addin/assets/icon-64.png differ diff --git a/system/word_addin/assets/icon-80.png b/system/word_addin/assets/icon-80.png new file mode 100644 index 0000000..b0654f3 Binary files /dev/null and b/system/word_addin/assets/icon-80.png differ diff --git a/system/word_addin/assets/logo-filled.png b/system/word_addin/assets/logo-filled.png new file mode 100644 index 0000000..60c0f01 Binary files /dev/null and b/system/word_addin/assets/logo-filled.png differ diff --git a/system/word_addin/commands.html b/system/word_addin/commands.html new file mode 100644 index 0000000..6f3a200 --- /dev/null +++ b/system/word_addin/commands.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/system/word_addin/commands.js b/system/word_addin/commands.js new file mode 100644 index 0000000..04513c3 --- /dev/null +++ b/system/word_addin/commands.js @@ -0,0 +1,2 @@ +!function(){var e={};e.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),Office.onReady((function(){})),("undefined"!=typeof self?self:"undefined"!=typeof window?window:void 0!==e.g?e.g:void 0).action=function(e){var n={type:Office.MailboxEnums.ItemNotificationMessageType.InformationalMessage,message:"Performed action.",icon:"Icon.80x80",persistent:!0};Office.context.mailbox.item.notificationMessages.replaceAsync("action",n),e.completed()}}(); +//# sourceMappingURL=commands.js.map \ No newline at end of file diff --git a/system/word_addin/commands.js.map b/system/word_addin/commands.js.map new file mode 100644 index 0000000..0f94cef --- /dev/null +++ b/system/word_addin/commands.js.map @@ -0,0 +1 @@ +{"version":3,"file":"commands.js","mappings":"YACA,IAAIA,EAAsB,CAAC,ECD3BA,EAAoBC,EAAI,WACvB,GAA0B,iBAAfC,WAAyB,OAAOA,WAC3C,IACC,OAAOC,MAAQ,IAAIC,SAAS,cAAb,EAChB,CAAE,MAAOC,GACR,GAAsB,iBAAXC,OAAqB,OAAOA,MACxC,CACA,CAPuB,GCOxBC,OAAOC,SAAQ,WACb,KAuBuB,oBAATC,KACVA,KACkB,oBAAXH,OACPA,YACkB,IAAXI,EAAAA,EACPA,EAAAA,OACAC,GAMJC,OA5BF,SAAgBC,GACd,IAAMC,EAAU,CACdC,KAAMR,OAAOS,aAAaC,4BAA4BC,qBACtDJ,QAAS,oBACTK,KAAM,aACNC,YAAY,GAIdb,OAAOc,QAAQC,QAAQC,KAAKC,qBAAqBC,aAAa,SAAUX,GAGxED,EAAMa,WACR,C","sources":["webpack://office-addin-taskpane-js/webpack/bootstrap","webpack://office-addin-taskpane-js/webpack/runtime/global","webpack://office-addin-taskpane-js/./src/commands/commands.js"],"sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","/*\n * Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n * See LICENSE in the project root for license information.\n */\n\n/* global global, Office, self, window */\n\nOffice.onReady(() => {\n // If needed, Office.js is ready to be called\n});\n\n/**\n * Shows a notification when the add-in command is executed.\n * @param event {Office.AddinCommands.Event}\n */\nfunction action(event) {\n const message = {\n type: Office.MailboxEnums.ItemNotificationMessageType.InformationalMessage,\n message: \"Performed action.\",\n icon: \"Icon.80x80\",\n persistent: true,\n };\n\n // Show a notification message\n Office.context.mailbox.item.notificationMessages.replaceAsync(\"action\", message);\n\n // Be sure to indicate when the add-in command function is complete\n event.completed();\n}\n\nfunction getGlobal() {\n return typeof self !== \"undefined\"\n ? self\n : typeof window !== \"undefined\"\n ? window\n : typeof global !== \"undefined\"\n ? global\n : undefined;\n}\n\nconst g = getGlobal();\n\n// The add-in command functions need to be available in global scope\ng.action = action;\n"],"names":["__webpack_require__","g","globalThis","this","Function","e","window","Office","onReady","self","global","undefined","action","event","message","type","MailboxEnums","ItemNotificationMessageType","InformationalMessage","icon","persistent","context","mailbox","item","notificationMessages","replaceAsync","completed"],"sourceRoot":""} \ No newline at end of file diff --git a/system/word_addin/manifest.xml b/system/word_addin/manifest.xml new file mode 100644 index 0000000..5d1117d --- /dev/null +++ b/system/word_addin/manifest.xml @@ -0,0 +1,79 @@ + + + 68b6ad53-4f72-42a8-a971-a9643c2f409a + 1.0.0.0 + AllTalk + en-US + + + + + + + https://www.contoso.com + + + + + + + + ReadWriteDocument + + + + + + + + + +