Blame Raw
Cohee · e3f41666 · · 326 lines (13.4 KB)
1 contributor
1import { doExtrasFetch, getApiUrl, modules } from '../../extensions.js';
2import { saveTtsProviderSettings } from './index.js';
3
4export { XTTSTtsProvider };
5
6class XTTSTtsProvider {
7 //########//
8 // Config //
9 //########//
10
11 settings;
12 ready = false;
13 voices = [];
14 separator = '. ';
15
16 /**
17 * Perform any text processing before passing to TTS engine.
18 * @param {string} text Input text
19 * @returns {string} Processed text
20 */
21 processText(text) {
22 // Replace fancy ellipsis with "..."
23 text = text.replace(/…/g, '...');
24 // Remove quotes
25 text = text.replace(/["“”‘’]/g, '');
26 // Replace multiple "." with single "."
27 text = text.replace(/\.+/g, '.');
28 return text;
29 }
30
31 languageLabels = {
32 'Arabic': 'ar',
33 'Brazilian Portuguese': 'pt',
34 'Chinese': 'zh-cn',
35 'Czech': 'cs',
36 'Dutch': 'nl',
37 'English': 'en',
38 'French': 'fr',
39 'German': 'de',
40 'Italian': 'it',
41 'Polish': 'pl',
42 'Russian': 'ru',
43 'Spanish': 'es',
44 'Turkish': 'tr',
45 'Japanese': 'ja',
46 'Korean': 'ko',
47 'Hungarian': 'hu',
48 'Hindi': 'hi',
49 };
50
51 defaultSettings = {
52 provider_endpoint: 'http://localhost:8020',
53 language: 'en',
54 temperature: 0.75,
55 length_penalty: 1.0,
56 repetition_penalty: 5.0,
57 top_k: 50,
58 top_p: 0.85,
59 speed: 1,
60 enable_text_splitting: true,
61 stream_chunk_size: 100,
62 voiceMap: {},
63 streaming: false,
64 };
65
66 get settingsHtml() {
67 let html = `
68 <label for="xtts_api_language">Language</label>
69 <select id="xtts_api_language">`;
70
71 for (let language in this.languageLabels) {
72 if (this.languageLabels[language] == this.settings?.language) {
73 html += `<option value="${this.languageLabels[language]}" selected="selected">${language}</option>`;
74 continue;
75 }
76
77 html += `<option value="${this.languageLabels[language]}">${language}</option>`;
78 }
79
80 html += `
81 </select>
82 <label">XTTS Settings:</label><br/>
83 <label for="xtts_tts_endpoint">Provider Endpoint:</label>
84 <input id="xtts_tts_endpoint" type="text" class="text_pole" maxlength="250" value="${this.defaultSettings.provider_endpoint}"/>
85 <span>Use <a target="_blank" href="https://github.com/daswer123/xtts-api-server">XTTSv2 TTS Server</a>.</span>
86 <label for="xtts_tts_streaming" class="checkbox_label">
87 <input id="xtts_tts_streaming" type="checkbox" />
88 <span>Streaming <small>(RVC not supported)</small></span>
89 </label>
90 <label for="xtts_speed">Speed: <span id="xtts_tts_speed_output">${this.defaultSettings.speed}</span></label>
91 <input id="xtts_speed" type="range" value="${this.defaultSettings.speed}" min="0.5" max="2" step="0.01" />
92
93 <label for="xtts_temperature">Temperature: <span id="xtts_tts_temperature_output">${this.defaultSettings.temperature}</span></label>
94 <input id="xtts_temperature" type="range" value="${this.defaultSettings.temperature}" min="0.01" max="1" step="0.01" />
95
96 <label for="xtts_length_penalty">Length Penalty: <span id="xtts_length_penalty_output">${this.defaultSettings.length_penalty}</span></label>
97 <input id="xtts_length_penalty" type="range" value="${this.defaultSettings.length_penalty}" min="0.5" max="2" step="0.1" />
98
99 <label for="xtts_repetition_penalty">Repetition Penalty: <span id="xtts_repetition_penalty_output">${this.defaultSettings.repetition_penalty}</span></label>
100 <input id="xtts_repetition_penalty" type="range" value="${this.defaultSettings.repetition_penalty}" min="1" max="10" step="0.1" />
101
102 <label for="xtts_top_k">Top K: <span id="xtts_top_k_output">${this.defaultSettings.top_k}</span></label>
103 <input id="xtts_top_k" type="range" value="${this.defaultSettings.top_k}" min="0" max="100" step="1" />
104
105 <label for="xtts_top_p">Top P: <span id="xtts_top_p_output">${this.defaultSettings.top_p}</span></label>
106 <input id="xtts_top_p" type="range" value="${this.defaultSettings.top_p}" min="0" max="1" step="0.01" />
107
108 <label for="xtts_stream_chunk_size">Stream Chunk Size: <span id="xtts_stream_chunk_size_output">${this.defaultSettings.stream_chunk_size}</span></label>
109 <input id="xtts_stream_chunk_size" type="range" value="${this.defaultSettings.stream_chunk_size}" min="100" max="400" step="1" />
110
111 <label for="xtts_enable_text_splitting" class="checkbox_label">
112 <input id="xtts_enable_text_splitting" type="checkbox" ${this.defaultSettings.enable_text_splitting ? 'checked' : ''} />
113 Enable Text Splitting
114 </label>
115 `;
116
117 return html;
118 }
119
120 onSettingsChange() {
121 // Used when provider settings are updated from UI
122 this.settings.provider_endpoint = $('#xtts_tts_endpoint').val();
123 this.settings.language = $('#xtts_api_language').val();
124
125 // Update the default TTS settings based on input fields
126 this.settings.speed = $('#xtts_speed').val();
127 this.settings.temperature = $('#xtts_temperature').val();
128 this.settings.length_penalty = $('#xtts_length_penalty').val();
129 this.settings.repetition_penalty = $('#xtts_repetition_penalty').val();
130 this.settings.top_k = $('#xtts_top_k').val();
131 this.settings.top_p = $('#xtts_top_p').val();
132 this.settings.stream_chunk_size = $('#xtts_stream_chunk_size').val();
133 this.settings.enable_text_splitting = $('#xtts_enable_text_splitting').is(':checked');
134 this.settings.streaming = $('#xtts_tts_streaming').is(':checked');
135
136 // Update the UI to reflect changes
137 $('#xtts_tts_speed_output').text(this.settings.speed);
138 $('#xtts_tts_temperature_output').text(this.settings.temperature);
139 $('#xtts_length_penalty_output').text(this.settings.length_penalty);
140 $('#xtts_repetition_penalty_output').text(this.settings.repetition_penalty);
141 $('#xtts_top_k_output').text(this.settings.top_k);
142 $('#xtts_top_p_output').text(this.settings.top_p);
143 $('#xtts_stream_chunk_size_output').text(this.settings.stream_chunk_size);
144
145 saveTtsProviderSettings();
146 this.changeTTSSettings();
147 }
148
149 async loadSettings(settings) {
150 // Pupulate Provider UI given input settings
151 if (Object.keys(settings).length == 0) {
152 console.info('Using default TTS Provider settings');
153 }
154
155 // Only accept keys defined in defaultSettings
156 this.settings = this.defaultSettings;
157
158 for (const key in settings) {
159 if (key in this.settings) {
160 this.settings[key] = settings[key];
161 } else {
162 throw `Invalid setting passed to TTS Provider: ${key}`;
163 }
164 }
165
166 const apiCheckInterval = setInterval(() => {
167 // Use Extras API if TTS support is enabled
168 if (modules.includes('tts') || modules.includes('xtts-tts')) {
169 const baseUrl = new URL(getApiUrl());
170 baseUrl.pathname = '/api/tts';
171 this.settings.provider_endpoint = baseUrl.toString();
172 $('#xtts_tts_endpoint').val(this.settings.provider_endpoint);
173 clearInterval(apiCheckInterval);
174 }
175 }, 2000);
176
177 // Set initial values from the settings
178 $('#xtts_tts_endpoint').val(this.settings.provider_endpoint);
179 $('#xtts_api_language').val(this.settings.language);
180 $('#xtts_speed').val(this.settings.speed);
181 $('#xtts_temperature').val(this.settings.temperature);
182 $('#xtts_length_penalty').val(this.settings.length_penalty);
183 $('#xtts_repetition_penalty').val(this.settings.repetition_penalty);
184 $('#xtts_top_k').val(this.settings.top_k);
185 $('#xtts_top_p').val(this.settings.top_p);
186 $('#xtts_enable_text_splitting').prop('checked', this.settings.enable_text_splitting);
187 $('#xtts_stream_chunk_size').val(this.settings.stream_chunk_size);
188 $('#xtts_tts_streaming').prop('checked', this.settings.streaming);
189
190 // Update the UI to reflect changes
191 $('#xtts_tts_speed_output').text(this.settings.speed);
192 $('#xtts_tts_temperature_output').text(this.settings.temperature);
193 $('#xtts_length_penalty_output').text(this.settings.length_penalty);
194 $('#xtts_repetition_penalty_output').text(this.settings.repetition_penalty);
195 $('#xtts_top_k_output').text(this.settings.top_k);
196 $('#xtts_top_p_output').text(this.settings.top_p);
197 $('#xtts_stream_chunk_size_output').text(this.settings.stream_chunk_size);
198
199 // Register input/change event listeners to update settings on user interaction
200 $('#xtts_tts_endpoint').on('input', () => { this.onSettingsChange(); });
201 $('#xtts_api_language').on('change', () => { this.onSettingsChange(); });
202 $('#xtts_speed').on('input', () => { this.onSettingsChange(); });
203 $('#xtts_temperature').on('input', () => { this.onSettingsChange(); });
204 $('#xtts_length_penalty').on('input', () => { this.onSettingsChange(); });
205 $('#xtts_repetition_penalty').on('input', () => { this.onSettingsChange(); });
206 $('#xtts_top_k').on('input', () => { this.onSettingsChange(); });
207 $('#xtts_top_p').on('input', () => { this.onSettingsChange(); });
208 $('#xtts_enable_text_splitting').on('change', () => { this.onSettingsChange(); });
209 $('#xtts_stream_chunk_size').on('input', () => { this.onSettingsChange(); });
210 $('#xtts_tts_streaming').on('change', () => { this.onSettingsChange(); });
211
212 await this.checkReady();
213
214 console.debug('XTTS: Settings loaded');
215 }
216
217 // Perform a simple readiness check by trying to fetch voiceIds
218 async checkReady() {
219 await Promise.allSettled([this.fetchTtsVoiceObjects(), this.changeTTSSettings()]);
220 }
221
222 async onRefreshClick() {
223 return;
224 }
225
226 //#################//
227 // TTS Interfaces //
228 //#################//
229
230 async getVoice(voiceName) {
231 if (this.voices.length == 0) {
232 this.voices = await this.fetchTtsVoiceObjects();
233 }
234 const match = this.voices.filter(
235 XTTSVoice => XTTSVoice.name == voiceName,
236 )[0];
237 if (!match) {
238 throw `TTS Voice name ${voiceName} not found`;
239 }
240 return match;
241 }
242
243 async generateTts(text, voiceId) {
244 const response = await this.fetchTtsGeneration(text, voiceId);
245 return response;
246 }
247
248 //###########//
249 // API CALLS //
250 //###########//
251 async fetchTtsVoiceObjects() {
252 const response = await doExtrasFetch(`${this.settings.provider_endpoint}/speakers`);
253 if (!response.ok) {
254 throw new Error(`HTTP ${response.status}: ${await response.json()}`);
255 }
256 const responseJson = await response.json();
257 return responseJson;
258 }
259
260 // Each time a parameter is changed, we change the configuration
261 async changeTTSSettings() {
262 if (!this.settings.provider_endpoint) {
263 return;
264 }
265
266 const response = await doExtrasFetch(
267 `${this.settings.provider_endpoint}/set_tts_settings`,
268 {
269 method: 'POST',
270 headers: {
271 'Content-Type': 'application/json',
272 'Cache-Control': 'no-cache',
273 },
274 body: JSON.stringify({
275 'temperature': this.settings.temperature,
276 'speed': this.settings.speed,
277 'length_penalty': this.settings.length_penalty,
278 'repetition_penalty': this.settings.repetition_penalty,
279 'top_p': this.settings.top_p,
280 'top_k': this.settings.top_k,
281 'enable_text_splitting': this.settings.enable_text_splitting,
282 'stream_chunk_size': this.settings.stream_chunk_size,
283 }),
284 },
285 );
286 return response;
287 }
288
289 async fetchTtsGeneration(inputText, voiceId) {
290 console.info(`Generating new TTS for voice_id ${voiceId}`);
291
292 if (this.settings.streaming) {
293 const params = new URLSearchParams();
294 params.append('text', inputText);
295 params.append('speaker_wav', voiceId);
296 params.append('language', this.settings.language);
297 return `${this.settings.provider_endpoint}/tts_stream/?${params.toString()}`;
298 }
299
300 const response = await doExtrasFetch(
301 `${this.settings.provider_endpoint}/tts_to_audio/`,
302 {
303 method: 'POST',
304 headers: {
305 'Content-Type': 'application/json',
306 'Cache-Control': 'no-cache', // Added this line to disable caching of file so new files are always played - Rolyat 7/7/23
307 },
308 body: JSON.stringify({
309 'text': inputText,
310 'speaker_wav': voiceId,
311 'language': this.settings.language,
312 }),
313 },
314 );
315 if (!response.ok) {
316 toastr.error(response.statusText, 'TTS Generation Failed');
317 throw new Error(`HTTP ${response.status}: ${await response.text()}`);
318 }
319 return response;
320 }
321
322 // Interface not used by XTTS TTS
323 async fetchTtsFromHistory(history_item_id) {
324 return Promise.resolve(history_item_id);
325 }
326}