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 @@
1+import { saveTtsProviderSettings } from './index.js';
2+
3+export { GptSovitsV2Provider };
4+
5+class 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 '.
44import { EdgeTtsProvider } from './edge.js';
55import { ElevenLabsTtsProvider } from './elevenlabs.js';
66import { SileroTtsProvider } from './silerotts.js';
7+import { GptSovitsV2Provider } from './gpt-sovits-v2.js';
78import { CoquiTtsProvider } from './coqui.js';
89import { SystemTtsProvider } from './system.js';
910import { NovelTtsProvider } from './novel.js';
@@ -91,6 +92,7 @@ const ttsProviders = {
9192 Edge: EdgeTtsProvider,
9293 ElevenLabs: ElevenLabsTtsProvider,
9394 GSVI: GSVITtsProvider,
95+ 'GPT-SoVITS-V2 (Unofficial)': GptSovitsV2Provider,
9496 Novel: NovelTtsProvider,
9597 OpenAI: OpenAITtsProvider,
9698 'OpenAI Compatible': OpenAICompatibleTtsProvider,