initial upload attempt

initial upload attempt
This commit is contained in:
erew123 committed 2024-06-06 21:40:12 +01:00
1 parent 84c19ed5a9
commit 9fd928f6e7
527 files changed
+37359

No files matched your search

@@ -0,0 +1 @@
Place the `alltalk.js` in your `\SillyTavern\public\scripts\extensions\tts\` folder, replacing the existing file.
@@ -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 = `<div class="at-settings-separator">AllTalk Settings</div>`;
html += `<div class="at-settings-row">
<div class="at-settings-option">
<label for="at_generation_method">AllTalk TTS Generation Method</label>
<select id="at_generation_method">
<option value="standard_generation">Standard Audio Generation (AT Narrator - Optional)</option>
<option value="streaming_enabled">Streaming Audio Generation (AT Narrator - Disabled)</option>
</select>
</div>
</div>`;
html += `<div class="at-settings-row">
<div class="at-settings-option">
<label for="at_narrator_enabled">AT Narrator</label>
<select id="at_narrator_enabled">
<option value="true">Enabled</option>
<option value="silent">Enabled (Silenced)</option>
<option value="false">Disabled</option>
</select>
</div>
<div class="at-settings-option">
<label for="at_narrator_text_not_inside">Text Not Inside * or " is</label>
<select id="at_narrator_text_not_inside">
<option value="character">Character</option>
<option value="narrator">Narrator</option>
<option value="silent">Silent</option>
</select>
</div>
</div>`;
html += `<div class="at-settings-row">
<div class="at-settings-option">
<label for="narrator_voice">Narrator Voice</label>
<select id="narrator_voice">`;
if (this.voices) {
for (let voice of this.voices) {
html += `<option value="${voice.voice_id}">${voice.name}</option>`;
}
}
html += `</select>
</div>
<div class="at-settings-option">
<label for="language_options">Language</label>
<select id="language_options">`;
for (let language in this.languageLabels) {
html += `<option value="${this.languageLabels[language]}" ${this.languageLabels[language] === this.settings?.language ? 'selected="selected"' : ''}>${language}</option>`;
}
html += `</select>
</div>
</div>`;
html += `<div class="at-settings-row">
<div class="at-settings-option">
<label for="rvc_character_voice">RVC Character</label>
<select id="rvc_character_voice">`;
if (this.rvcVoices) {
for (let rvccharvoice of this.rvcVoices) {
html += `<option value="${rvccharvoice.voice_id}">${rvccharvoice.name}</option>`;
}
}
html += `</select>
</div>
<div class="at-settings-option">
<label for="rvc_narrator_voice">RVC Narrator</label>
<select id="rvc_narrator_voice">`;
if (this.rvcVoices) {
for (let rvcnarrvoice of this.rvcVoices) {
html += `<option value="${rvcnarrvoice.voice_id}">${rvcnarrvoice.name}</option>`;
}
}
html += `</select>
</div>
</div>`;
html += `<div class="at-model-endpoint-row">
<div class="at-model-option">
<label for="switch_model">Switch Model</label>
<select id="switch_model">
<!-- Options will be dynamically populated -->
</select>
</div>
<div class="at-endpoint-option">
<label for="at_server">AllTalk Endpoint:</label>
<input id="at_server" type="text" class="text_pole" maxlength="80" value="${this.settings.provider_endpoint}"/>
</div>
</div>`;
html += `<div class="at-model-endpoint-row">
<div class="at-settings-option">
<label for="low_vram">Low VRAM</label>
<input id="low_vram" type="checkbox"/>
</div>
<div class="at-settings-option">
<label for="deepspeed">DeepSpeed</label>
<input id="deepspeed" type="checkbox"/>
</div>
<div class="at-settings-option status-option">
<span>Status: <span id="status_info">Ready</span></span>
</div>
<div class="at-settings-option empty-option">
<!-- This div remains empty for spacing -->
</div>
</div>`;
html += `<div class="at-website-row">
<div class="at-website-option">
<span>AllTalk <a target="_blank" href="${this.settings.provider_endpoint}">Config & Docs</a>.</span>
</div>
<div class="at-website-option">
<span>AllTalk <a target="_blank" href="https://github.com/erew123/alltalk_tts/">Website</a>.</span>
</div>
</div>`;
html += `<div class="at-website-row">
<div class="at-website-option">
<span>- If you <strong>change your TTS engine</strong> in AllTalk, you will need to <strong>Reload</strong> (button above) and re-select your voices.</span><br><br>
<span>- Assuming the server is <strong>Status: Ready</strong>, most problems will be resolved by hitting Reload and selecting voices that match the loaded TTS engine.</span><br><br>
<span>- <strong>Text-generation-webui</strong> users - Uncheck <strong>Enable TTS</strong> in the TGWUI interface, or you will hear 2x voices and file names being generated.</span>
</div>
</div>`;
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;
}
}
}
@@ -0,0 +1 @@
Place the `alltalk.js` in your `\SillyTavern\public\scripts\extensions\tts\` folder, replacing the existing file.
@@ -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 = `<div class="at-settings-separator">AllTalk Settings</div>`;
html += `<div class="at-settings-row">
<div class="at-settings-option">
<label for="at_generation_method">AllTalk TTS Generation Method</label>
<select id="at_generation_method">
<option value="standard_generation">Standard Audio Generation (AT Narrator - Optional)</option>
<option value="streaming_enabled">Streaming Audio Generation (AT Narrator - Disabled)</option>
</select>
</div>
</div>`;
html += `<div class="at-settings-row">
<div class="at-settings-option">
<label for="at_narrator_enabled">AT Narrator</label>
<select id="at_narrator_enabled">
<option value="true">Enabled</option>
<option value="false">Disabled</option>
</select>
</div>
<div class="at-settings-option">
<label for="at_narrator_text_not_inside">Text Not Inside * or " is</label>
<select id="at_narrator_text_not_inside">
<option value="narrator">Narrator</option>
<option value="character">Character</option>
</select>
</div>
</div>`;
html += `<div class="at-settings-row">
<div class="at-settings-option">
<label for="narrator_voice">Narrator Voice</label>
<select id="narrator_voice">`;
if (this.voices) {
for (let voice of this.voices) {
html += `<option value="${voice.voice_id}">${voice.name}</option>`;
}
}
html += `</select>
</div>
<div class="at-settings-option">
<label for="language_options">Language</label>
<select id="language_options">`;
for (let language in this.languageLabels) {
html += `<option value="${this.languageLabels[language]}" ${this.languageLabels[language] === this.settings?.language ? 'selected="selected"' : ''}>${language}</option>`;
}
html += `</select>
</div>
</div>`;
html += `<div class="at-model-endpoint-row">
<div class="at-model-option">
<label for="switch_model">Switch Model</label>
<select id="switch_model">
<option value="api_tts">API TTS</option>
<option value="api_local">API Local</option>
<option value="xttsv2_local">XTTSv2 Local</option>
</select>
</div>
<div class="at-endpoint-option">
<label for="at_server">AllTalk Endpoint:</label>
<input id="at_server" type="text" class="text_pole" maxlength="80" value="${this.settings.provider_endpoint}"/>
</div>
</div>`;
html += `<div class="at-model-endpoint-row">
<div class="at-settings-option">
<label for="low_vram">Low VRAM</label>
<input id="low_vram" type="checkbox"/>
</div>
<div class="at-settings-option">
<label for="deepspeed">DeepSpeed</label>
<input id="deepspeed" type="checkbox"/>
</div>
<div class="at-settings-option status-option">
<span>Status: <span id="status_info">Ready</span></span>
</div>
<div class="at-settings-option empty-option">
<!-- This div remains empty for spacing -->
</div>
</div>`;
html += `<div class="at-website-row">
<div class="at-website-option">
<span>AllTalk <a target="_blank" href="${this.settings.provider_endpoint}">Config & Docs</a>.</span>
</div>
<div class="at-website-option">
<span>AllTalk <a target="_blank" href="https://github.com/erew123/alltalk_tts/">Website</a>.</span>
</div>
</div>`;
html += `<div class="at-website-row">
<div class="at-website-option">
<span><strong>Text-generation-webui</strong> users - Uncheck <strong>Enable TTS</strong> in Text-generation-webui.</span>
</div>
</div>`;
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;
}
}
}
@@ -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.
Binary file not shown.
Binary file not shown.
@@ -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.
+18
View File
@@ -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"
}
Binary file not shown.
Binary file not shown.
+692
View File
@@ -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'<audio src="file/{audio_path}" controls autoplay></audio>'
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'<audio src="file/{audio_path}" controls autoplay></audio>'
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'<audio src="{generate_response}" controls {autoplay}></audio>'
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'<img[^>]*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'<audio src="{generate_response}?{int(time.time())}" controls {autoplay}></audio>'
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("<audio"):
if params["tgwui"]["tgwui_show_text"]:
reply = history["internal"][i][1]
history["visible"][i] = [
history["visible"][i][0],
f"{visible_reply.split('</audio>')[0]}</audio>\n\n{reply}",
]
else:
history["visible"][i] = [
history["visible"][i][0],
f"{visible_reply.split('</audio>')[0]}</audio>",
]
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"<p><a href='{alltalk_protocol}{alltalk_ip_port}'>AllTalk Server & Documentation Link</a><a href='{alltalk_protocol}{alltalk_ip_port}'></a>")
# 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)
@@ -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
}
}
+864
View File
@@ -0,0 +1,864 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="icon" href="./static/favicon.ico" type="image/x-icon">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AllTalk TTS</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 1200px;
margin: 40px auto;
padding: 20px;
background-color: #f4f4f4;
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
h1,
h2 {
color: black;
text-decoration: underline;
}
.form-row {
display: flex;
flex-wrap: wrap;
margin-bottom: 15px;
}
.form-group {
flex: 1;
margin-right: 10px;
min-width: 220px;
/* Ensure a minimum width for the form groups */
}
.form-group label {
display: block;
margin-bottom: 5px;
}
.form-group select,
.form-group input[type="text"],
.form-group textarea {
width: 100%;
padding: 5px;
box-sizing: border-box;
/* Include padding and border in the element's total width and height */
}
.button-group {
flex-basis: 100%;
/* Ensure button groups take up the full width */
display: flex;
justify-content: flex-start;
/* Align buttons to the start */
align-items: center;
/* Adjusted to center align buttons */
gap: 10px;
/* Add space between buttons */
margin-top: 10px;
}
.button-group button {
padding: 10px 20px;
font-size: 14px;
border: none;
border-radius: 4px;
cursor: pointer;
background-color: #4caf50;
color: white;
transition: background-color 0.3s;
flex: 1;
/* Allow buttons to take up equal space */
}
button:hover {
background-color: #45a049;
}
#refreshSettingsBtn {
background-color: #3498db;
color: #fff;
}
#submit {
padding: 10px 20px;
font-size: 16px;
font-weight: bold;
width: 100%;
/* Make the submit button take the full width */
margin-top: 10px;
}
#outputFile,
#modelSelect {
width: 100%;
/* Set to full width */
box-sizing: border-box;
/* Include padding and border in the element's total width and height */
}
#outputFile {
margin-right: 10px;
}
p,
span {
color: #555;
font-size: 16px;
margin-top: 0;
}
code {
font-family: Arial, sans-serif;
background-color: #e9e9e9;
border: none;
box-shadow: none;
outline: none;
color: #3366ff;
font-size: 16px;
}
pre {
background-color: #f8f8f8;
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
overflow-x: auto;
font-size: 14px;
}
ul {
color: #555;
list-style-type: square;
margin-left: 2px;
}
li {
font-size: 16px;
margin-bottom: 8px;
}
a {
color: #0077cc;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
strong {
font-weight: bold;
}
#container {
max-width: 1000px;
margin: 50px auto;
padding: 20px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
form {
display: flex;
flex-direction: column;
gap: 4px;
}
#outputFileContainer {
display: flex;
flex-direction: column;
gap: 4px;
}
label {
font-weight: bold;
font-size: 14px;
padding: 2px;
}
textarea,
input,
select,
button {
padding: 4px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 4px;
}
textarea {
resize: vertical;
}
p {
margin-top: 20px;
}
#outputFilePath {
font-weight: bold;
}
#audioSource {
display: block;
margin: auto;
}
#outputFilePath {
display: none;
}
table {
border-collapse: collapse;
width: 100%;
margin-bottom: 20px;
}
th,
td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
/* Responsive styling */
@media (max-width: 768px) {
.form-group {
flex: 1 1 100%;
/* Make each form group take full width on small screens */
margin-right: 0;
}
.button-group {
flex-direction: column;
/* Stack buttons vertically on small screens */
align-items: stretch;
}
.button-group button {
width: 100%;
}
}
</style>
<script>
// Declare variables in the global scope
let protocol, host, port, cleanedBaseUrl, currentUrl;
document.addEventListener('DOMContentLoaded', function () {
// Use URL constructor for robust parsing
currentUrl = new URL(window.location.href);
console.log('CurrentURL', currentUrl.href);
protocol = currentUrl.protocol; // "http:" or "https:"
host = currentUrl.hostname; // "127.0.0.1"
port = currentUrl.port || (protocol === 'https:' ? '443' : '80'); // "7851" or default ports
const basePath = currentUrl.pathname.split('/')[1] ? `/${currentUrl.pathname.split('/')[1]}` : '';
console.log('Protocol:', protocol);
console.log('Host:', host);
console.log('Port:', port);
console.log('Base Path:', basePath);
cleanedBaseUrl = `${protocol}//${host}:${port}${basePath}`;
console.log('CleanedBaseURL', cleanedBaseUrl);
// Fetch settings and populate the form, then set the URLs
fetchSettings().then(() => {
setUrls();
}).catch(console.error);
});
async function fetchSettings() {
try {
const response = await fetch('/settings-json');
console.log("Response is:", response);
if (!response.ok) {
throw new Error('Network response was not ok ' + response.statusText);
}
const data = await response.json();
console.log("Data is:", data);
populateForm(data);
} catch (error) {
console.error('Failed to fetch settings:', error);
throw error;
}
}
function populateForm(data) {
return new Promise((resolve, reject) => {
try {
const deleteOutputWavsElem = document.getElementById('delete_output_wavs');
const gradioInterfaceElem = document.getElementById('gradio_interface');
const gradioPortNumberElem = document.getElementById('gradio_port_number');
const apiPortNumberElem = document.getElementById('api_port_number');
if (deleteOutputWavsElem) {
deleteOutputWavsElem.value = data.delete_output_wavs;
}
if (gradioInterfaceElem) {
gradioInterfaceElem.value = data.gradio_interface ? 'true' : 'false';
}
if (gradioPortNumberElem) {
gradioPortNumberElem.value = data.gradio_port_number;
console.log("Populated gradioPortNumberElem.value:", gradioPortNumberElem.value); // Add logging here
}
if (apiPortNumberElem) {
apiPortNumberElem.value = data.api_def.api_port_number;
}
resolve();
} catch (error) {
reject(error);
}
});
}
// Function to set the URLs
function setUrls() {
const ttsGeneratorLink = document.getElementById('ttsGeneratorLink');
ttsGeneratorLink.href = `${cleanedBaseUrl}/static/tts_generator/tts_generator.html`;
ttsGeneratorLink.textContent = 'TTS Generator';
const GradioLink = document.getElementById('GradioLink');
const GradioLinkDark = document.getElementById('GradioLinkDark');
const gradioPortNumberElem = document.getElementById('gradio_port_number');
if (gradioPortNumberElem && gradioPortNumberElem.value) {
GradioLink.href = `${protocol}//${host}:${gradioPortNumberElem.value}`;
GradioLinkDark.href = `${protocol}//${host}:${gradioPortNumberElem.value}?__theme=dark`;
} else {
GradioLink.href = `${protocol}//${host}:7852/gradio`;
GradioLinkDark.href = `${protocol}//${host}:7852/gradio?__theme=dark`;
console.error('Gradio port number element not found or empty.');
}
GradioLink.textContent = 'Gradio (Light theme)';
GradioLinkDark.textContent = 'Gradio (Dark theme)';
}
</script>
</head>
<body>
<h1 id="toc">AllTalk TTS</h1>
<hr>
<div style="display: flex; justify-content: flex-start; align-items: flex-start;">
<div style="flex: 1; padding-left: 20px;">
<h3>Useful links</h3>
<ul>
<li><a href="" id="ttsGeneratorLink" target="_blank" rel="noopener">TTS Generator</a></li>
<li><a href="" id="GradioLink" target="_blank" rel="noopener">Gradio (Light theme)</a></li>
<li><a href="" id="GradioLinkDark" target="_blank" rel="noopener">Gradio (Dark theme)</a></li>
<li>Documentation is inside the Gradio interface</li>
</li>
</ul>
</div>
<div style="flex: 1; padding-right: 20px;">
<h3 id="index">Support Links</h3>
<ul>
<li><a href="https://github.com/erew123/alltalk_tts" target="_blank" rel="noopener">Github</a>
</li>
<li><a href="https://github.com/erew123/alltalk_tts/issues/25" target="_blank"
rel="noopener">Changelog</a></li>
<li><a href="https://github.com/erew123/alltalk_tts/issues/74" target="_blank" rel="noopener">Feature
Requests</a></li>
<li><a href="https://github.com/erew123/alltalk_tts?#-help-with-problems" target="_blank"
rel="noopener">Help Documents</a></li>
</ul>
</div>
</div>
<hr>
<div id="container">
<form method="post" action="/tts-demo-request" id="ttsForm">
<div class="form-row">
<div class="form-group">
<label for="text">Text:</label>
<textarea id="text" name="text" rows="4" required></textarea>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="voice">Voice:</label>
<select id="voice" name="voice" required></select>
</div>
<div class="form-group">
<label for="rvcvoice">RVC Voice:</label>
<select id="rvcvoice" name="rvccharacter_voice_gen" required></select>
</div>
<div class="form-group">
<label for="language">Language:</label>
<select id="language" name="language" value="English" required>
<option value="en" selected>English</option>
<option value="ar">Arabic</option>
<option value="zh-cn">Chinese</option>
<option value="cs">Czech</option>
<option value="nl">Dutch</option>
<option value="fr">French</option>
<option value="de">German</option>
<option value="hu">Hungarian</option>
<option value="it">Italian</option>
<option value="ja">Japanese</option>
<option value="ko">Korean</option>
<option value="pl">Polish</option>
<option value="pt">Portuguese</option>
<option value="ru">Russian</option>
<option value="es">Spanish</option>
<option value="tr">Turkish</option>
</select>
</div>
<div class="form-group">
<label for="streaming">Streaming:</label>
<select id="streaming" name="streaming" required>
<option value="false" selected>No</option>
<option value="true">Yes</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="outputFile">Output Filename:</label>
<input type="text" id="outputFile" name="output_file" value="demo_output" required>
</div>
<div class="form-group">
<label for="engineSelect">Available Engines:</label>
<select id="engineSelect" name="engineSelect" required></select>
</div>
<div class="form-group">
<label for="modelSelect">Available Models:</label>
<select id="modelSelect" name="modelSelect" required></select>
</div>
</div>
<div class="form-row button-group">
<button id="refreshSettingsBtn" type="button">Refresh Settings</button>
<button id="changeEngineBtn" type="button">Change Engine</button>
<button id="changeModelBtn" type="button">Change Model</button>
</div>
<div class="form-row">
<button id="submit" type="submit">Generate TTS</button>
</div>
<audio controls autoplay id="audioSource">
<source type="audio/wav" />
Your browser does not support the audio element.
</audio>
<span id="outputFilePath">{{ output_file_path }}</span>
</form>
</div>
<div id="container">
<form method="post" action="/update-settings" id="settingsForm" enctype="application/x-www-form-urlencoded">
<div class="form-row">
<div class="form-group">
<label for="delete_output_wavs"">Del WAV's older than:</label>
<select id=" delete_output_wavs" name="delete_output_wavs" required>
<option value="Disabled">Disabled</option>
<option value="1 Day">1 Day</option>
<option value="2 Days">2 Days</option>
<option value="3 Days">3 Days</option>
<option value="4 Days">4 Days</option>
<option value="5 Days">5 Days</option>
<option value="6 Days">6 Days</option>
<option value="7 Days">7 Days</option>
<option value="14 Days">14 Days</option>
<option value="21 Days">21 Days</option>
<option value="28 Days">28 Days</option>
</select>
</div>
<div class="form-group">
<label for="api_port_number">API Port Number:</label>
<input type="number" id="api_port_number" name="api_port_number" required
style="width: 228px; height: 20px;">
</div>
<div class="form-group">
<label for="gradio_interface">Gradio Interface:</label>
<select id="gradio_interface" name="gradio_interface" required>
<option value="true">Enabled</option>
<option value="false">Disabled</option>
</select>
</div>
<div class="form-group">
<label for="gradio_port_number">Gradio Port Number:</label>
<input type="number" id="gradio_port_number" name="gradio_port_number" required
style="width: 228px; height: 20px;">
</div>
</div>
<div class="form-row">
<button id="submit" type="submit">Update Settings</button>
</div>
</form>
</div>
<hr>
<h3 id="🛠️-about-this-project">🛠️ <strong>About this project</strong></h3>
<p>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&#39;t covered by the documentation or existing
discussions, and it&#39;s not related to a bug or feature request, I&#39;ll do my best to assist as time allows.
</p>
<hr>
<h3 id="💖-showing-your-support">💖 Showing Your Support</h3>
<p>If AllTalk has been helpful to you, consider showing your support through a donation on my <a
href="https://ko-fi.com/erew123">Ko-fi page</a>. Your support is greatly appreciated and helps ensure the
continued development and improvement of AllTalk.</p>
<hr>
<h3 id="⚠️-showing-your-support">⚠️ Changing Available Engine</h3>
<div>
<p>Changing the <b>Available Engine</b> on this web page will cause the Gradio interface to become
unsynchronized with the TTS Engine process <b>until</b> AllTalk is restarted. This may result in issues with
the Gradio interface, including warnings such as <b><i>[Errno 10048] error while attempting to bind on
address</i></b> or <b><i>runner</i></b> errors when exiting AllTalk. For best results, please use
the Gradio interface to change and manage your <b>Available Engine</b>"
</p>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
// Use URL constructor for robust parsing
const currentUrl = new URL(window.location.href);
console.log('CurrentURL', currentUrl.href);
protocol = currentUrl.protocol; // "http:" or "https:"
host = currentUrl.hostname; // "127.0.0.1"
port = currentUrl.port || (protocol === 'https:' ? '443' : '80'); // "7851" or default ports
// Determine the base path by taking only the first part of the path
const pathParts = currentUrl.pathname.split('/');
const basePath = pathParts.length > 1 && pathParts[1] ? `/${pathParts[1]}` : '';
console.log('Protocol:', protocol);
console.log('Host:', host);
console.log('Port:', port);
console.log('Base Path:', basePath);
cleanedBaseUrl = `${protocol}//${host}:${port}`;
console.log('CleanedBaseURL', cleanedBaseUrl);
setUrls();
});
document.addEventListener('DOMContentLoaded', function () {
const engineSelect = document.getElementById('engineSelect');
const modelSelect = document.getElementById('modelSelect');
const changeEngineBtn = document.getElementById('changeEngineBtn');
const changeModelBtn = document.getElementById('changeModelBtn');
const refreshSettingsBtn = document.getElementById('refreshSettingsBtn');
console.log('Protocol:', protocol);
console.log('Host:', host);
console.log('Port:', port);
// Function to dynamically construct the URL for fetching voices
function getVoicesUrl() {
return `${cleanedBaseUrl}/api/voices`;
}
// Function to dynamically construct the URL for fetching RVC voices
function getrvcVoicesUrl() {
return `${cleanedBaseUrl}/api/rvcvoices`;
}
// Function to fetch current settings and populate the dropdown
function fetchAndPopulateSettings() {
fetch(`${cleanedBaseUrl}/api/currentsettings`)
.then(response => response.json())
.then(data => {
// Clear existing options
engineSelect.innerHTML = '';
modelSelect.innerHTML = '';
// Populate the dropdown with available engines
data.engines_available.forEach(engine => {
const option = document.createElement('option');
option.value = engine;
option.textContent = engine;
engineSelect.appendChild(option);
});
// Populate the dropdown with available models
data.models_available.forEach(model => {
const option = document.createElement('option');
option.value = model.name;
option.textContent = model.name;
modelSelect.appendChild(option);
});
// Set the current loaded engine and model as selected
engineSelect.value = data.current_engine_loaded;
modelSelect.value = data.current_model_loaded;
})
.catch(error => {
console.error('Error fetching current settings:', error);
// Retry fetching settings after 5 seconds on error
setTimeout(fetchAndPopulateSettings, 5000);
});
}
// Function to reload voices
function reloadVoices() {
// Clear existing options
document.getElementById('voice').innerHTML = '';
document.getElementById('rvcvoice').innerHTML = '';
// Fetch voices and populate the dropdown
fetch(getVoicesUrl())
.then(response => response.json())
.then(data => {
const voiceSelect = document.getElementById('voice');
data.voices.forEach(voice => {
const option = document.createElement('option');
option.value = voice;
option.textContent = voice;
voiceSelect.appendChild(option);
});
resetChangeEngineBtn();
})
.catch(error => console.error('Error fetching voices:', error));
// Fetch RVC voices and populate the dropdown
fetch(getrvcVoicesUrl())
.then(response => response.json())
.then(data => {
const rvcVoiceSelect = document.getElementById('rvcvoice');
data.rvcvoices.forEach(rvcvoice => {
const option = document.createElement('option');
option.value = rvcvoice;
option.textContent = rvcvoice;
rvcVoiceSelect.appendChild(option);
});
resetChangeEngineBtn();
})
.catch(error => console.error('Error fetching RVC voices:', error));
}
// Function to check if engine is ready
function checkEngineReady() {
fetch(`${cleanedBaseUrl}/api/ready`, {
method: 'GET'
})
.then(response => response.text())
.then(data => {
if (data === 'Ready') {
console.log('Engine is ready');
reloadVoices(); // Call the reloadVoices function
fetchAndPopulateSettings(); // Refresh settings when engine is ready
} else {
console.log('Engine not ready yet, rechecking...');
setTimeout(() => checkEngineReady(), 1000); // Recheck after 1 second
}
})
.catch(error => {
console.error('Error checking engine readiness:', error);
setTimeout(() => checkEngineReady(), 1000); // Recheck after 1 second even on error
});
}
// Function to change the engine button to "Changing Engine" state
function setChangeEngineBtnChanging() {
changeEngineBtn.style.backgroundColor = 'gray';
changeEngineBtn.textContent = 'Changing Engine';
changeEngineBtn.disabled = true;
}
// Function to reset the engine button to its original state
function resetChangeEngineBtn() {
changeEngineBtn.style.backgroundColor = '#4caf50';
changeEngineBtn.textContent = 'Change Engine';
changeEngineBtn.disabled = false;
}
// Function to change the model button to "Changing Model" state
function setChangeModelBtnChanging() {
changeModelBtn.style.backgroundColor = 'gray';
changeModelBtn.textContent = 'Changing Model';
changeModelBtn.disabled = true;
}
// Function to reset the model button to its original state
function resetChangeModelBtn() {
changeModelBtn.style.backgroundColor = '#4caf50';
changeModelBtn.textContent = 'Change Model';
changeModelBtn.disabled = false;
}
// Event listener for the "Change Engine" button
changeEngineBtn.addEventListener('click', function () {
const selectedEngine = engineSelect.value;
setChangeEngineBtnChanging(); // Set button to "Changing Engine" state
fetch(`${cleanedBaseUrl}/api/enginereload?engine=${encodeURIComponent(selectedEngine)}`, {
method: 'POST'
})
.catch(error => console.error('Error sending engine reload request:', error));
console.log('Engine reload request sent, waiting 15 seconds before checking readiness...');
setTimeout(() => {
checkEngineReady();
}, 15000); // Wait 15 seconds before starting to check
});
// Event listener for the "Change Model" button
changeModelBtn.addEventListener('click', function () {
const selectedModel = modelSelect.value;
setChangeModelBtnChanging(); // Set button to "Changing Model" state
fetch(`${cleanedBaseUrl}/api/reload?tts_method=${encodeURIComponent(selectedModel)}`, {
method: 'POST'
})
.then(response => response.json())
.then(data => {
if (data.status === 'model-success') {
console.log('Model changed successfully');
setTimeout(() => {
reloadVoices(); // Call the reloadVoices function after a delay
fetchAndPopulateSettings(); // Refresh settings
}, 12000); // Wait 12 second before refreshing settings and voices
} else {
console.error('Failed to change model');
resetChangeModelBtn(); // Reset button to original state
}
})
.catch(error => {
console.error('Error changing model:', error);
resetChangeModelBtn(); // Reset button to original state
});
});
// Event listener for the "Refresh Settings" button
refreshSettingsBtn.addEventListener('click', function () {
fetchAndPopulateSettings();
reloadVoices();
resetChangeEngineBtn(); // Reset button to original state
resetChangeModelBtn(); // Reset button to original state
});
// Fetch settings and populate the dropdown on page load
fetchAndPopulateSettings();
reloadVoices();
});
</script>
<script>
// Audio player
const audioPlayer = document.getElementById('audioSource');
function enableLoader(enable) {
// Change the submit button text and disable it while loading
const submit = document.getElementById('submit');
submit.disabled = enable ? true : false;
submit.style.opacity = enable ? 0.7 : 1.0;
submit.innerText = enable ? 'Generating...' : 'Generate TTS';
// Disable the audio player while loading
audioPlayer.disabled = enable ? true : false;
audioPlayer.style.opacity = enable ? 0.7 : 1.0;
audioPlayer.style.pointerEvents = enable ? 'none' : 'auto';
}
audioPlayer.addEventListener('canplay', () => {
enableLoader(false);
audioPlayer.play();
});
audioPlayer.addEventListener('abort', () => {
enableLoader(false);
});
audioPlayer.addEventListener('error', (e) => {
enableLoader(false);
});
// Streaming selector
const streaming = document.getElementById('streaming');
streaming.addEventListener('change', (event) => {
const outputFileContainer = document.getElementById('outputFileContainer');
if (event.target.value === 'true') {
outputFileContainer.style.display = 'none';
} else {
outputFileContainer.style.display = 'flex';
}
});
// Form submit event listener
const form = document.getElementById('ttsForm');
form.addEventListener('submit', async (event) => {
event.preventDefault();
enableLoader(true);
audioPlayer.pause();
const formData = new FormData(form);
// Get and clean the text input from the textarea
let textInput = formData.get('text');
let cleanedText = textInput.replace(/ \- | \– /g, ' '); // Replace hyphens
cleanedText = cleanedText.replace(/%/g, " percent");
//cleanedText = cleanedText.replace(/([!?.])\1+/g, '$1');
//cleanedText = cleanedText.replace(/[^a-zA-Z0-9\s\.,;:!?\-\'"À-ÿ\u0400-\u04FF]/g, '');
cleanedText = cleanedText.replace(/[^a-zA-Z0-9\s\.,;:!?\-\'"À-ÿ\u0400-\u04FF]\$/g, '')
cleanedText = cleanedText.replace(/\n+/g, ' ');
cleanedText = cleanedText.replace(/\.\s'/g, ".'"); // Remove space between period and single quote
//cleanedText = cleanedText.replace(/'/g, ""); // Remove all single quotes
// Set the cleaned text back to the form data
formData.set('text', cleanedText);
// Check if streaming or not and handle accordingly
if (formData.get('streaming') === 'true') {
// For streaming, update the audio player src
const searchParams = new URLSearchParams(formData).toString();
audioPlayer.src = `${cleanedBaseUrl}/api/tts-generate-streaming?${searchParams}`;
audioPlayer.load(); // Reload the audio player
} else {
// Add hard-coded settings to the formData object
formData.append('text_filtering', 'standard');
formData.append('narrator_enabled', false);
formData.append('autoplay', false);
formData.append('autoplay_volume', '0.1');
// Change the field name from "voice" to "character_voice_gen"
const voice = formData.get('voice');
formData.delete('voice');
formData.append('character_voice_gen', voice);
// Update the field name from "output_file" to "output_file_name"
const outputFile = formData.get('output_file');
formData.delete('output_file');
formData.append('output_file_name', outputFile);
formData.delete('text');
formData.set('text_input', cleanedText);
// Convert "output_file_timestamp" value to boolean
formData.append('output_file_timestamp', false);
console.log('Form Data:');
for (const [key, value] of formData.entries()) {
console.log(`${key}: ${value}`);
}
// Send the POST request to the API endpoint
try {
const response = await fetch(`${cleanedBaseUrl}/api/tts-generate`, {
method: 'POST',
body: formData,
cache: "no-store"
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// Process the response
const result = await response.json();
console.log('Response:', result);
// Update output file path and audio player source using the response data
const outputFilePath = document.getElementById('outputFilePath');
outputFilePath.textContent = result.output_file_path;
audioPlayer.src = cleanedBaseUrl + result.output_file_url;
audioPlayer.load();
} catch (error) {
console.error('Error:', error);
// Handle the error (e.g., display an error message to the user)
}
}
});
</script>
<hr>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

+248
View File
@@ -0,0 +1,248 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AllTalk Startup Settings</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 5px;
padding: 5px;
background-color: #f4f4f4;
}
h1 {
color: #333;
margin-bottom: 10px;
}
h1,
h2,
h3 {
text-decoration: underline;
margin-bottom: 10px;
}
form {
max-width: 975px;
margin: 0 auto;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 5px;
/* Adjusted the gap to create more space */
justify-content: start;
}
.radio-group {
grid-column: span 2;
/* Span both columns */
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
label {
display: block;
margin-top: 2px;
color: #555;
}
select,
input,
button {
width: 100%;
padding: 3px;
margin-top: 1px;
margin-bottom: 3px;
box-sizing: border-box;
font-size: 11px;
}
button {
background-color: #4caf50;
color: white;
border: none;
padding: 5px;
cursor: pointer;
font-size: 12px;
grid-column: span 4;
}
.warning {
grid-column: span 4;
/* Span all 4 columns */
text-align: center;
/* Center-align the text */
color: red;
/* Adjusted color for emphasis */
font-weight: bold;
/* Make the text bold */
margin-top: 10px;
/* Add some space between the button and the warning */
}
</style>
</head>
<body>
<h3>AllTalk Startup Settings</h3><br>
<form action="/update-settings" method="post">
<label for="activate" style="color: #b273fa;">TG-webui Activate TTS:</label>
<select id="activate" name="activate" required="">
<option {% if data.activate %}selected{% endif %} value="true">True</option>
<option {% if not data.activate %}selected{% endif %} value="false">False</option>
</select>
<label for="autoplay" style="color: #b273fa;">&nbsp; &nbsp; TG-webui Autoplay TTS:</label>
<select id="autoplay" name="autoplay" required="">
<option {% if data.autoplay %}selected{% endif %} value="true">True</option>
<option {% if not data.autoplay %}selected{% endif %} value="false">False</option>
</select>
<label for="show_text" style="color: #b273fa;">TG-webui Show Text:</label>
<select id="show_text" name="show_text" required="">
<option {% if data.show_text %}selected{% endif %} value="true">True</option>
<option {% if not data.show_text %}selected{% endif %} value="false">False</option>
</select>
<label for="voice" style="color: #b273fa;">&nbsp; &nbsp; TG-webui Default voice:</label>
<select id="voice" name="voice" required="">
{% for file in wav_files %}
<option value="{{ file }}" {% if data.voice==file %}selected{% endif %}>{{ file }}</option>
{% endfor %}
</select>
<label for="language" style="color: #b273fa;">TG-webui Default Language:</label>
<select id="language" name="language" required="">
<option value="Arabic" {% if data.language=='Arabic' %}selected{% endif %}>Arabic</option>
<option value="Chinese" {% if data.language=='Chinese' %}selected{% endif %}>Chinese</option>
<option value="Czech" {% if data.language=='Czech' %}selected{% endif %}>Czech</option>
<option value="Dutch" {% if data.language=='Dutch' %}selected{% endif %}>Dutch</option>
<option value="English" {% if data.language=='English' %}selected{% endif %}>English</option>
<option value="French" {% if data.language=='French' %}selected{% endif %}>French</option>
<option value="German" {% if data.language=='German' %}selected{% endif %}>German</option>
<option value="Hungarian" {% if data.language=='Hungarian' %}selected{% endif %}>Hungarian</option>
<option value="Italian" {% if data.language=='Italian' %}selected{% endif %}>Italian</option>
<option value="Japanese" {% if data.language=='Japanese' %}selected{% endif %}>Japanese</option>
<option value="Korean" {% if data.language=='Korean' %}selected{% endif %}>Korean</option>
<option value="Polish" {% if data.language=='Polish' %}selected{% endif %}>Polish</option>
<option value="Portuguese" {% if data.language=='Portuguese' %}selected{% endif %}>Portuguese</option>
<option value="Russian" {% if data.language=='Russian' %}selected{% endif %}>Russian</option>
<option value="Spanish" {% if data.language=='Spanish' %}selected{% endif %}>Spanish</option>
<option value="Turkish" {% if data.language=='Turkish' %}selected{% endif %}>Turkish</option>
</select>
<label for="narrator_voice" style="color: #b273fa;">&nbsp; &nbsp; TG-webui Narrator voice:</label>
<select id="narrator_voice" name="narrator_voice" required="">
{% for file in wav_files %}
<option value="{{ file }}" {% if data.narrator_voice==file %}selected{% endif %}>{{ file }}</option>
{% endfor %}
</select>
<label for="remove_trailing_dots" style="color: #b273fa;">TG-webui Remove Trailing Dots:</label>
<select id="remove_trailing_dots" name="remove_trailing_dots" required="">
<option {% if data.remove_trailing_dots %}selected{% endif %} value="true">True</option>
<option {% if not data.remove_trailing_dots %}selected{% endif %} value="false">False</option>
</select>
<label for="narrator_enabled" style="color: #b273fa;">&nbsp; &nbsp; TG-webui Narrator enabled:</label>
<select id="narrator_enabled" name="narrator_enabled" required="">
<option {% if data.narrator_enabled %}selected{% endif %} value="true">True</option>
<option {% if not data.narrator_enabled %}selected{% endif %} value="false">False</option>
</select>
<label for="delete_output_wavs" style="color: #33cc33;">Del WAV's older than:</label>
<select id="delete_output_wavs" name="delete_output_wavs" required="">
<option value="Disabled" {% if data.delete_output_wavs=='Disabled' %}selected{% endif %}>Disabled</option>
<option value="1 Day" {% if data.delete_output_wavs=='1 Day' %}selected{% endif %}>1 Day</option>
<option value="2 Days" {% if data.delete_output_wavs=='2 Days' %}selected{% endif %}>2 Days</option>
<option value="3 Days" {% if data.delete_output_wavs=='3 Days' %}selected{% endif %}>3 Days</option>
<option value="4 Day" {% if data.delete_output_wavs=='4 Days' %}selected{% endif %}>4 Days</option>
<option value="5 Day" {% if data.delete_output_wavs=='5 Days' %}selected{% endif %}>5 Days</option>
<option value="6 Day" {% if data.delete_output_wavs=='6 Days' %}selected{% endif %}>6 Days</option>
<option value="7 Days" {% if data.delete_output_wavs=='7 Days' %}selected{% endif %}>7 Days</option>
<option value="14 Days" {% if data.delete_output_wavs=='14 Days' %}selected{% endif %}>14 Days</option>
<option value="21 Days" {% if data.delete_output_wavs=='21 Days' %}selected{% endif %}>21 Days</option>
<option value="28 Days" {% if data.delete_output_wavs=='28 Days' %}selected{% endif %}>28 Days</option>
</select>
<label for="low_vram" style="color: #33cc33;">&nbsp; &nbsp; Low VRAM:</label>
<select id="low_vram" name="low_vram" required="">
<option {% if data.low_vram %}selected{% endif %} value="true">True</option>
<option {% if not data.low_vram %}selected{% endif %} value="false">False</option>
</select>
<label for="local_temperature" style="color: red;">Local Model temperature:</label>
<select id="local_temperature" name="local_temperature" required="">
{% for temp in range(0, 101, 5) %}
<option value="{{ temp/100 }}" {% if data.local_temperature|float == temp/100 %}selected{% endif %}>{{ "{:.2f}".format(temp/100) }}</option>
{% endfor %}
</select>
<label for="local_repetition_penalty" style="color: red;">&nbsp; &nbsp; Local Model repetition penalty:</label>
<select id="local_repetition_penalty" name="local_repetition_penalty" required="">
{% for repetition in range(10, 210, 10) %}
<option value="{{ repetition/10 }}" {% if data.local_repetition_penalty|float == repetition/10 %}selected{% endif %}>{{ repetition/10 }}</option>
{% endfor %}
</select>
<label for="deepspeed_activate" style="color: red;">DeepSpeed Activate:</label>
<select id="deepspeed_activate" name="deepspeed_activate" required="">
<option {% if data.deepspeed_activate %}selected{% endif %} value="true">True</option>
<option {% if not data.deepspeed_activate %}selected{% endif %} value="false">False</option>
</select>
<label for="output_folder_wav" style="color: red;">&nbsp; &nbsp; WAV Output Folder:</label>
<input id="output_folder_wav" name="output_folder_wav" required="" type="text"
value="{{ data.output_folder_wav }}" />
<input type="hidden" id="tts_model_loaded" name="tts_model_loaded" value="{{ data.tts_model_loaded }}" />
<!-- Continue updating other dropdowns and inputs in a similar manner -->
<label for="tts_model_name" style="color: red;"> API TTS Model Name:</label>
<input id="tts_model_name" name="tts_model_name" required="" type="text" value="{{ data.tts_model_name }}" />
<label for="model_version"
style="color: red; font-size: 16px; line-height: 1; vertical-align: bottom; margin-bottom: 0;">&nbsp;
&nbsp;&nbsp;API Local Model Folder:</label>
<span style="font-size: 14px; vertical-align: bottom; margin-bottom: 0; margin-top: 2px;">{{
modeldownload_model_path }}</span>
<label for="port_number" style="color: red;"> Port Number:</label>
<input id="port_number" name="port_number" required="" type="text" value="{{ data.port_number }}" /><br>
<div class="radio-group">
<label for="tts_method_api_local" style="color: #33cc33;">Model: <span
style="color: black; font-weight: normal;">API Local</span>:</label>
<input id="tts_method_api_local_true" {% if data.tts_method_api_local %}checked{% endif %} name="tts_method"
type="radio" value="api_local" />
</div>
<div class="radio-group">
<label for="tts_method_api_tts" style="color: #33cc33;">&nbsp; &nbsp;&nbsp;Model: <span
style="color: black; font-weight: normal;">API TTS</span>:</label>
<input id="tts_method_api_tts_true" {% if data.tts_method_api_tts %}checked{% endif %} name="tts_method"
type="radio" value="api_tts" />
</div>
<div class="radio-group">
<label for="tts_method_xtts_local" style="color: #33cc33;">Model: <span
style="color: black; font-weight: normal;">XTTS Local</span>:</label>
<input id="tts_method_xtts_local_true" {% if data.tts_method_xtts_local %}checked{% endif %}
name="tts_method" type="radio" value="xtts_local" />
</div>
<button type="submit">Update Settings</button>
</form>
<p class="warning">WARNING: Changing RED items or setting DeepSpeed to True on a system without DeepSpeed installed,
may cause errors.</p>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

+2
View File
@@ -0,0 +1,2 @@
{
}
+2
View File
@@ -0,0 +1,2 @@
{
}
+186
View File
@@ -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()
+186
View File
@@ -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()
+92
View File
@@ -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
+720
View File
@@ -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.
+18
View File
@@ -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"
}
@@ -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
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

+869
View File
@@ -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()
+16
View File
@@ -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.<br><br>
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.<br><br>
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.<br><br>
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.<br><br>
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)**. <br><br>
Your support is greatly appreciated and helps ensure the continued development and improvement of AllTalk.
"""
gr.Markdown(about)
+76
View File
@@ -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()
File diff suppressed because it is too large. Load diff
@@ -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<br>
**Swap TTS Engine**<br>
> 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.<br>
**Load Different Model**<br>
> 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.<br>
### 🟨 RVC Pipeline<br>
**RVC Character Voice**<br>
> 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.<br>
**RVC Narrator Voice**<br>
> Similar to the RVC Character Voice, this setting applies to the narrator voice in the RVC pipeline.<br>
### 🟨 Generation Settings<br>
**Generation Mode**<br>
> 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.<br>
**Language/Model not multi-language**<br>
> 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.<br>
**Narrator Enabled/Disabled**<br>
> 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):<br>
> **Narrated Text** : Text enclosed in asterisks *Narrated text*.<br>
> **Character Text** : Text enclosed in double quotes "Character text".<br>
> **Text-Not-Inside**: Any text not enclosed in asterisks or double quotes. The handling of this text is customizable.<br>
**Narrator Text-not-inside**<br>
> Configures how text that is not enclosed in asterisks or double quotes is handled. Detailed explanations are available in the "Narrator Function" help tab.<br>
**Auto-Stop Current Generation**<br>
> 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<br>
**Character (Main TTS) Voice**<br>
> Select the character voice from the available voices within the currently loaded TTS engine and model.<br>
**Narrator Voice**<br>
> Choose the narrator voice from the available voices within the currently loaded TTS engine and model.<br>
"""
generation_help2 = """
### 🟨 Additional Settings
**Text Filtering**<br>
> **None**: No filtering. Raw text is sent to the TTS engine, which may result in odd sounds with some special characters.<br>
> **HTML**: For HTML content, using HTML entities like emojis or text characters.<br>
> **Standard**: Human-readable text with basic filtering to clean up special characters.<br>
**Play Locally or Remotely**<br>
> Choose to play the generated speech locally or on the terminal/console where the server is running.<br>
**Remote Play Volume**<br>
> Adjust the volume for remote playback.<br>
**Output File Name**<br>
> Specify the name for the output file.<br>
**Include Timestamp**<br>
> 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.<br>
**Speed**<br>
> Adjust the speed of the generated speech. This option is not available if the selected TTS engine doesn't support it.<br>
**Pitch**<br>
>Adjust the pitch of the generated speech. This option is not available if the selected TTS engine doesn't support it.<br>
**Temperature**<br>
> 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.<br>
**Repetition Penalty**<br>
> Set the penalty for repetitive sequences in the generated speech. This option is not available if the selected TTS engine doesn't support it.<br>
### 🟨 Output and Controls
**TTS Result**<br>
> View the result of the TTS generation process here.<br>
**Playback Controls**<br>
> Use these controls to play, pause, and navigate through the generated speech.<br>
**Light/Dark Mode**<br>
> Toggle between light and dark mode for the interface.<br>
**Refresh Server Settings**<br>
>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.<br>
**Interrupt TTS Generation**<br>
> Manually stop the current TTS generation process, similar to the "Auto-Stop Current Generation" setting but applied manually.<br>
"""
gr.Markdown(generation_blurb)
with gr.Row():
gr.Markdown(generation_help)
gr.Markdown(generation_help2)
+59
View File
@@ -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).<br>
> - 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.<br>
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**Debian-based systems** `sudo apt install libaio-dev`<br>
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**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)
+580
View File
@@ -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.<br>
```
{
"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.<br>
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.<br>
If the connection is available, then you would:<br>
> 1) Pull the current settings and populate those into a paramaters variabele. **/api/currentsettings**<br>
2) Pull the current voices and populate those into a paramaters variabele. **/api/voices**<br>
3) Pull the current rvcvoices and populate those into a paramaters variabele. **/api/rvcvoices**<br>
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.<br>
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!<br>
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.<br>
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.<br>
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.<br>
If you are NOT presenting all the variables/options to the user, then you have 2x options when sending to **/api/tts-generate**:<br>
> 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.<br>
OR<br>
>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.<br>
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<br>
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.<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **URL**: `http://{ipaddress}:{port}/api/ready`<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **Method**: `GET`<br>
> `curl -X GET "http://127.0.0.1:7851/api/ready"`
Response: `Ready` or `Unloaded`
### 🟠 Standard Voices List Endpoint<br>
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.
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **URL**: `http://{ipaddress}:{port}/api/voices`<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **Method**: `GET`<br>
> `curl -X GET "http://127.0.0.1:7851/api/voices"`
```
{
"status": "success",
"voices": ["voice1", "voice2", "voice3"]
}
```
### 🟠 RVC Voices List Endpoint<br>
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.
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **URL**: `http://{ipaddress}:{port}/api/rvcvoices`<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **Method**: `GET`<br>
> `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<br>
Retrieve the current settings of the TTS engine.
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **URL**: `http://{ipaddress}:{port}/api/currentsettings`<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **Method**: `GET`<br>
> `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<br>
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.
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **URL**: `http://{ipaddress}:{port}/v1/audio/speech`<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **Method**: `POST`<br>
`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:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**model** (string): The TTS model to use. Currently ignored, but is required in the request.<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**input** (string): The text to generate audio for. Maximum length is 4096 characters.<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**voice** (string): The voice to use when generating the audio. Supported voices are ["alloy", "echo", "fable", "nova", "onyx", "shimmer"].<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**response_format** (string, optional): The format of the audio. Audio will be transcoded to the requested format.<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**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.<br><br>
```
{
"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<br>
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.
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **URL**: `http://{ipaddress}:{port}/api/stop-generation`<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **Method**: `PUT`<br>
> `curl -X PUT "http://127.0.0.1:7851/api/stop-generation"`
```
{
"message": "Cancelling current TTS generation"
}
```
### 🟠 Reload Configuration Endpoint<br>
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.
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **URL**: `http://{ipaddress}:{port}/api/reload_config`<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **Method**: `GET`<br>
> `curl -X GET "http://127.0.0.1:7851/api/reload_config"`
`Config file reloaded successfully`
### 🟠 Reload/Swap model<br>
Can be used to load in/swap to one of the models presented by /api/currentsettings in the `models_available` list.
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **URL**: `http://{ipaddress}:{port}/api/reload?tts_method=x`<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **Method**: `POST`<br>
> `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<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **URL**: `http://{ipaddress}:{port}/api/deepspeed`<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **Method**: `POST`<br>
> `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<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **URL**: `http://{ipaddress}:{port}/api/lowvramsetting`<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **Method: `POST`<br>
> `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)<br>
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.<br>
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.<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **URL**: `http://{ipaddress}:{port}/api/tts-generate`<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **Method**: `POST`<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **Content-Type**: application/x-www-form-urlencoded<br>
### 🟠 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:<br><br>
`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:<br><br>
`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"`<br>
> If your text contains double quotes, escape them with &#92;" (see the narrator example).<br>
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:<br><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-d "text_input=*This is text spoken by the narrator* &#92;"This is text spoken by the character&#92;". This is text not inside quotes."
#### **text_filtering**
> Filter for text. Options:<br><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**none**: No filtering. Raw text is sent to the TTS engine.<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**standard**: Basic level of filtering to clean up special characters.<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**html**: For HTML content with entities like &quot;.<br><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-d "text_filtering=none"<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-d "text_filtering=standard"<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-d "text_filtering=html"<br>
#### **character_voice_gen**
> The name of the character's voice file (WAV format).<br><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-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.<br><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-d "rvccharacter_voice_gen=folder\\voice1.pth"
#### **narrator_enabled**
> Enable or disable the narrator function. If true, minimum text filtering is set to standard.<br><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-d "narrator_enabled=true"<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-d "narrator_enabled=false"
#### **narrator_voice_gen**
> The name of the narrator's voice file (WAV format).<br><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-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.<br><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-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:<br><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**character**: Treat as character speech.<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**narrator**: Treat as narrator speech.<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**silent**: Ignore text not inside quotes or asterisks.<br><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-d "text_not_inside=character"<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-d "text_not_inside=narrator"<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-d "text_not_inside=silent"<br>
#### **language**
> Choose the language for TTS. Options:<br><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**ar**: Arabic<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**zh-cn**: Chinese (Simplified)<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**cs**: Czech<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**nl**: Dutch<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**en**: English<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**fr**: French<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**de**: German<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**hi**: Hindi (limited support)<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**hu**: Hungarian<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**it**: Italian<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**ja**: Japanese<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**ko**: Korean<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**pl**: Polish<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**pt**: Portuguese<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**ru**: Russian<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**es**: Spanish<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**tr**: Turkish<br><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-d "language=en"
#### **output_file_name***
> The name of the output file (excluding the .wav extension).<br><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;--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.<br><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;--d "output_file_timestamp=true"<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;--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.<br><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;--d "autoplay=true"<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;--d "autoplay=false"
#### **autoplay_volume**
> Set the autoplay volume. Should be between 0.1 and 1.0.<br><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;--d "autoplay_volume=0.8"
#### **speed**
> Set the speed of the generated audio. Should be between 0.25 and 2.0.<br><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;--d "speed=1.0"
#### **pitch**
> Set the pitch of the generated audio. Should be between -10 and 10.<br><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;--d "pitch=0"
#### **temperature**
> Set the temperature for the TTS engine. Should be between 0.1 and 1.0.<br><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;--d "temperature=0.75"
#### **repetition_penalty**
> Set the repetition penalty for the TTS engine. Should be between 1.0 and 20.0.<br><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;--d "repetition_penalty=10"
### 🟠 TTS Generation Response
> The API returns a JSON object with the following properties:<br><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**status**: Indicates whether the generation was successful (generate-success) or failed (generate-failure).<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**output_file_path**: The on-disk location of the generated WAV file.<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**output_file_url**: The HTTP location for accessing the generated WAV file for browser playback.<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**output_cache_url**: The HTTP location for accessing the generated WAV file as a pushed download.<br>
```
{
"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"
}
```
<br>
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.
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **URL**: `http://{ipaddress}:{port}/api/tts-generate-streaming`<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **Method**: POST<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **Content-Type**: application/x-www-form-urlencoded<br>
> `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:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**text**: The text to convert to speech.<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**voice**: The voice type to use.<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**language**: The language for the TTS.<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**output_file**: The name of the output file.<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**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)
+114
View File
@@ -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
@@ -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"}
]
+284
View File
@@ -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",
)
+180
View File
@@ -0,0 +1,180 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OpenAI API/AllTalk TTS API Test</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #1e1e1e;
color: #c7c7c7;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
h1 {
color: #ffffff;
text-align: center;
margin-bottom: 20px;
}
.container {
background-color: #2b2b2b;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
max-width: 800px;
width: 100%;
}
label {
font-weight: bold;
display: block;
margin: 10px 0 5px;
color: #ffffff;
}
input[type="text"], input[type="number"], textarea, select {
width: calc(100% - 20px);
padding: 10px;
margin-bottom: 20px;
border: 1px solid #555;
border-radius: 4px;
background-color: #3b3b3b;
color: #ffffff;
}
input[type="submit"] {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
}
input[type="submit"]:hover {
background-color: #45a049;
}
.config, .response {
background-color: #3b3b3b;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
margin-bottom: 20px;
}
.form-group {
display: flex;
justify-content: space-between;
}
.form-group > div {
flex: 1;
margin-right: 10px;
}
.form-group > div:last-child {
margin-right: 0;
}
</style>
</head>
<body>
<div class="container">
<h1>OpenAI API/AllTalk TTS API Test</h1>
<div class="config form-group">
<div>
<label for="ip">IP Address:</label>
<input type="text" id="ip" name="ip" value="localhost" required>
</div>
<div>
<label for="port">Port Number:</label>
<input type="number" id="port" name="port" value="7851" required>
</div>
</div>
<form id="ttsForm">
<label for="model">Model:</label>
<input type="text" id="model" name="model" value="some-model" required><br>
<label for="input">Input Text:</label>
<textarea id="input" name="input" rows="4" required>Hello, this is a test.</textarea><br>
<div class="form-group">
<div>
<label for="voice">Voice:</label>
<select id="voice" name="voice" required>
<option value="alloy">Alloy</option>
<option value="echo">Echo</option>
<option value="fable">Fable</option>
<option value="nova">Nova</option>
<option value="onyx">Onyx</option>
<option value="shimmer">Shimmer</option>
</select>
</div>
<div>
<label for="response_format">Response Format:</label>
<select id="response_format" name="response_format" required>
<option value="wav">WAV</option>
<option value="mp3">MP3</option>
<option value="ogg">OGG</option>
<option value="flac">FLAC</option>
<option value="aac">AAC</option>
<option value="m4a">M4A</option>
</select>
</div>
<div>
<label for="speed">Speed:</label>
<input type="number" id="speed" name="speed" value="1.0" step="0.1" required><br>
</div>
</div>
<label for="autoplay">Auto-play:</label>
<input type="checkbox" id="autoplay" name="autoplay"><br><br>
<input type="submit" value="Submit"><br><br>
</form>
<div id="response" class="response"></div>
</div>
<script>
document.getElementById('ttsForm').addEventListener('submit', async function (event) {
event.preventDefault();
const formData = new FormData(event.target);
const jsonData = {};
formData.forEach((value, key) => {
jsonData[key] = key === 'speed' ? parseFloat(value) : value;
});
const ip = document.getElementById('ip').value;
const port = document.getElementById('port').value;
const url = `http://${ip}:${port}/v1/audio/speech`;
const responseDiv = document.getElementById('response');
responseDiv.innerHTML = 'Sending request...';
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(jsonData),
});
if (response.ok) {
const blob = await response.blob();
const url = URL.createObjectURL(blob);
const audio = new Audio(url);
audio.controls = true;
if (document.getElementById('autoplay').checked) {
audio.autoplay = true;
}
responseDiv.innerHTML = '';
responseDiv.appendChild(audio);
} else {
const errorText = await response.text();
responseDiv.innerHTML = 'Error: ' + errorText;
}
} catch (error) {
responseDiv.innerHTML = 'Error: ' + error.message;
}
});
</script>
</body>
</html>
@@ -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"
@@ -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"
@@ -0,0 +1,4 @@
jsonlines>=4.0.0
networkx>=3.0.0
protobuf==4.25.3
typing_extensions>=4.9.0
+269
View File
@@ -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()
+12
View File
@@ -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"
}
]
}
@@ -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"
]
}
]
}
+463
View File
@@ -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
@@ -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"
}
}
@@ -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.<br>
### 🟧 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.<br>
### 🟧 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.<br>
> When `Disabled`, your output wav files will be left untouched.<br>
> 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.<br>
""")
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
@@ -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."
}
]
}
@@ -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"
]
}
]
}
Loaded 100 of 527 files, more files were not shown because too many files have changed in this diff. Show more