Blame Raw
Cohee · e3f41666 · · 215 lines (6.2 KB)
1 contributor
1import { saveTtsProviderSettings } from './index.js';
2
3export { GptSovitsV2Provider };
4
5class GptSovitsV2Provider {
6 //########//
7 // Config //
8 //########//
9
10 settings;
11 ready = false;
12 voices = [];
13 separator = '. ';
14 audioElement = document.createElement('audio');
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 return text;
23 }
24
25 audioFormats = ['wav', 'ogg', 'silk', 'mp3', 'flac'];
26
27 languageLabels = {
28 'Auto': 'auto',
29 };
30
31 langKey2LangCode = {
32 'zh': 'zh-CN',
33 'en': 'en-US',
34 'ja': 'ja-JP',
35 'ko': 'ko-KR',
36 };
37
38
39 defaultSettings = {
40 provider_endpoint: 'http://localhost:9880',
41 format: 'wav',
42 lang: 'auto',
43 streaming: false,
44 text_lang: 'zh',
45 prompt_lang: 'zh',
46
47 };
48
49 get settingsHtml() {
50 let html = `
51
52 <label for="tts_endpoint">Provider Endpoint:</label>
53 <input id="tts_endpoint" type="text" class="text_pole" maxlength="250" height="300" value="${this.defaultSettings.provider_endpoint}"/>
54 <span>Use <a target="_blank" href="https://github.com/v3ucn/GPT-SoVITS-V2">GPT-SoVITS-V2</a>(Unofficial).</span><br/>
55 <label for="text_lang">Text Lang(Inference text language):</label>
56 <input id="text_lang" type="text" class="text_pole" maxlength="250" height="300" value="${this.defaultSettings.text_lang}"/>
57 <label for="text_lang">Prompt Lang(Reference audio text language):</label>
58 <input id="prompt_lang" type="text" class="text_pole" maxlength="250" height="300" value="${this.defaultSettings.prompt_lang}"/>
59 <br/>
60
61 `;
62
63 return html;
64 }
65
66 onSettingsChange() {
67 // Used when provider settings are updated from UI
68 this.settings.provider_endpoint = $('#tts_endpoint').val();
69 this.settings.text_lang = $('#text_lang').val();
70 this.settings.prompt_lang = $('#prompt_lang').val();
71
72
73 saveTtsProviderSettings();
74 this.changeTTSSettings();
75 }
76
77 async loadSettings(settings) {
78 // Pupulate Provider UI given input settings
79 if (Object.keys(settings).length == 0) {
80 console.info('Using default TTS Provider settings');
81 }
82
83 // Only accept keys defined in defaultSettings
84 this.settings = this.defaultSettings;
85
86 for (const key in settings) {
87 if (key in this.settings) {
88 this.settings[key] = settings[key];
89 } else {
90 console.debug(`Ignoring non-user-configurable setting: ${key}`);
91 }
92 }
93
94 // Set initial values from the settings
95 $('#tts_endpoint').val(this.settings.provider_endpoint).on('change', this.onSettingsChange.bind(this));
96 $('#text_lang').val(this.settings.text_lang).on('change', this.onSettingsChange.bind(this));
97 $('#prompt_lang').val(this.settings.prompt_lang).on('change', this.onSettingsChange.bind(this));
98
99 await this.checkReady();
100
101 console.info('ITS: Settings loaded');
102 }
103
104 // Perform a simple readiness check by trying to fetch voiceIds
105 async checkReady() {
106 await Promise.allSettled([this.fetchTtsVoiceObjects(), this.changeTTSSettings()]);
107 }
108
109 async onRefreshClick() {
110 return await this.checkReady();
111 }
112
113 //#################//
114 // TTS Interfaces //
115 //#################//
116
117 async getVoice(voiceName) {
118 if (this.voices.length == 0) {
119 this.voices = await this.fetchTtsVoiceObjects();
120 }
121
122
123 const match = this.voices.filter(
124 v => v.name == voiceName,
125 )[0];
126 console.log(match);
127 if (!match) {
128 throw `TTS Voice name ${voiceName} not found`;
129 }
130 return match;
131 }
132
133
134 async generateTts(text, voiceId) {
135 const response = await this.fetchTtsGeneration(text, voiceId);
136 return response;
137 }
138
139 //###########//
140 // API CALLS //
141 //###########//
142 async fetchTtsVoiceObjects() {
143 const response = await fetch(`${this.settings.provider_endpoint}/speakers`);
144 console.info(response);
145
146 if (!response.ok) {
147 throw new Error(`HTTP ${response.status}: ${await response.json()}`);
148 }
149 const responseJson = await response.json();
150
151
152 this.voices = responseJson;
153
154 return responseJson;
155 }
156
157 // Each time a parameter is changed, we change the configuration
158 async changeTTSSettings() {
159 }
160
161 /**
162 * Fetch TTS generation from the API.
163 * @param {string} inputText Text to generate TTS for
164 * @param {string} voiceId Voice ID to use (model_type&speaker_id))
165 * @returns {Promise<Response|string>} Fetch response
166 */
167
168
169 async fetchTtsGeneration(inputText, voiceId, lang = null, forceNoStreaming = false) {
170 console.info(`Generating new TTS for voice_id ${voiceId}`);
171
172 function replaceSpeaker(text) {
173 return text.replace(/\[.*?\]/gu, '');
174 }
175
176 let prompt_text = replaceSpeaker(voiceId);
177
178 const params = {
179 text: inputText,
180 prompt_text: prompt_text,
181 ref_audio_path: './参考音频/' + voiceId + '.wav',
182 text_lang: this.settings.text_lang,
183 prompt_lang: this.settings.prompt_lang,
184 text_split_method: 'cut5',
185 batch_size: 1,
186 media_type: 'ogg',
187 streaming_mode: 'true',
188 };
189
190
191 const url = `${this.settings.provider_endpoint}/`;
192
193 const response = await fetch(
194 url,
195 {
196 method: 'POST',
197 headers: {
198 'Content-Type': 'application/json',
199 },
200 body: JSON.stringify(params), // Convert parameter objects to JSON strings
201 },
202 );
203 if (!response.ok) {
204 toastr.error(response.statusText, 'TTS Generation Failed');
205 throw new Error(`HTTP ${response.status}: ${await response.text()}`);
206 }
207 return response;
208 }
209
210
211 // Interface not used
212 async fetchTtsFromHistory(history_item_id) {
213 return Promise.resolve(history_item_id);
214 }
215}