Merge pull request #2807 from v3ucn/GPT-SoVITS-V2 TTS API for the GPT-SoVITS-V2 model zero-shot

1abbd54f69ce424eeb43d2eb800395d9cf9c659a

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
2 files changed, +228 -0Ignore whitespace
public/scripts/extensions/tts/gpt-sovits-v2.js+226 -0
@@ -0,0 +1,226 @@
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);
96 $('#text_lang').val(this.settings.text_lang);
97 $('#prompt_lang').val(this.settings.prompt_lang);
98
99
100 await this.checkReady();
101
102 console.info('ITS: Settings loaded');
103 }
104
105 // Perform a simple readiness check by trying to fetch voiceIds
106 async checkReady() {
107 await Promise.allSettled([this.fetchTtsVoiceObjects(), this.changeTTSSettings()]);
108 }
109
110 async onRefreshClick() {
111 return;
112 }
113
114 //#################//
115 // TTS Interfaces //
116 //#################//
117
118 async getVoice(voiceName) {
119
120
121
122 if (this.voices.length == 0) {
123 this.voices = await this.fetchTtsVoiceObjects();
124 }
125
126
127
128 const match = this.voices.filter(
129 v => v.name == voiceName,
130 )[0];
131 console.log(match);
132 if (!match) {
133 throw `TTS Voice name ${voiceName} not found`;
134 }
135 return match;
136 }
137
138
139
140 async generateTts(text, voiceId) {
141 const response = await this.fetchTtsGeneration(text, voiceId);
142 return response;
143 }
144
145 //###########//
146 // API CALLS //
147 //###########//
148 async fetchTtsVoiceObjects() {
149 const response = await fetch(`${this.settings.provider_endpoint}/speakers`);
150 console.info(response);
151
152 if (!response.ok) {
153 throw new Error(`HTTP ${response.status}: ${await response.json()}`);
154 }
155 const responseJson = await response.json();
156
157
158 this.voices = responseJson;
159
160 return responseJson;
161 }
162
163 // Each time a parameter is changed, we change the configuration
164 async changeTTSSettings() {
165 }
166
167 /**
168 * Fetch TTS generation from the API.
169 * @param {string} inputText Text to generate TTS for
170 * @param {string} voiceId Voice ID to use (model_type&speaker_id))
171 * @returns {Promise<Response|string>} Fetch response
172 */
173
174
175
176
177 async fetchTtsGeneration(inputText, voiceId, lang = null, forceNoStreaming = false) {
178 console.info(`Generating new TTS for voice_id ${voiceId}`);
179
180 function replaceSpeaker(text) {
181 return text.replace(/\[.*?\]/gu, '');
182 }
183
184 let prompt_text = replaceSpeaker(voiceId);
185
186 const streaming = this.settings.streaming;
187
188 const params = {
189 text: inputText,
190 prompt_text: prompt_text,
191 ref_audio_path: './参考音频/' + voiceId + '.wav',
192 text_lang: this.settings.text_lang,
193 prompt_lang: this.settings.prompt_lang,
194 text_split_method: 'cut5',
195 batch_size: 1,
196 media_type: 'ogg',
197 streaming_mode: 'true',
198 };
199
200
201 const url = `${this.settings.provider_endpoint}/`;
202
203 const response = await fetch(
204 url,
205 {
206 method: 'POST',
207 headers: {
208 'Content-Type': 'application/json',
209 },
210 body: JSON.stringify(params), // Convert parameter objects to JSON strings
211 },
212 );
213 if (!response.ok) {
214 toastr.error(response.statusText, 'TTS Generation Failed');
215 throw new Error(`HTTP ${response.status}: ${await response.text()}`);
216 }
217 return response;
218 }
219
220
221
222 // Interface not used
223 async fetchTtsFromHistory(history_item_id) {
224 return Promise.resolve(history_item_id);
225 }
226}
public/scripts/extensions/tts/index.js+2 -0
@@ -4,6 +4,7 @@ import { delay, escapeRegex, getBase64Async, getStringHash, onlyUnique } from '.
4import { EdgeTtsProvider } from './edge.js';4import { EdgeTtsProvider } from './edge.js';
5import { ElevenLabsTtsProvider } from './elevenlabs.js';5import { ElevenLabsTtsProvider } from './elevenlabs.js';
6import { SileroTtsProvider } from './silerotts.js';6import { SileroTtsProvider } from './silerotts.js';
7import { GptSovitsV2Provider } from './gpt-sovits-v2.js';
7import { CoquiTtsProvider } from './coqui.js';8import { CoquiTtsProvider } from './coqui.js';
8import { SystemTtsProvider } from './system.js';9import { SystemTtsProvider } from './system.js';
9import { NovelTtsProvider } from './novel.js';10import { NovelTtsProvider } from './novel.js';
@@ -91,6 +92,7 @@ const ttsProviders = {
91 Edge: EdgeTtsProvider,92 Edge: EdgeTtsProvider,
92 ElevenLabs: ElevenLabsTtsProvider,93 ElevenLabs: ElevenLabsTtsProvider,
93 GSVI: GSVITtsProvider,94 GSVI: GSVITtsProvider,
95 'GPT-SoVITS-V2 (Unofficial)': GptSovitsV2Provider,
94 Novel: NovelTtsProvider,96 Novel: NovelTtsProvider,
95 OpenAI: OpenAITtsProvider,97 OpenAI: OpenAITtsProvider,
96 'OpenAI Compatible': OpenAICompatibleTtsProvider,98 'OpenAI Compatible': OpenAICompatibleTtsProvider,