diff --git a/public/scripts/extensions/tts/edge.js b/public/scripts/extensions/tts/edge.js new file mode 100644 index 000000000..cfa6b22cc --- /dev/null +++ b/public/scripts/extensions/tts/edge.js @@ -0,0 +1,142 @@ +import { getRequestHeaders } from "../../../script.js" +import { getApiUrl } from "../../extensions.js" +import { doExtrasFetch, modules } from "../../extensions.js" +import { getPreviewString } from "./index.js" + +export { EdgeTtsProvider } + +class EdgeTtsProvider { + //########// + // Config // + //########// + + settings + voices = [] + separator = ' . ' + audioElement = document.createElement('audio') + + defaultSettings = { + voiceMap: {} + } + + get settingsHtml() { + let html = `Microsoft Edge TTS Provider
` + return html + } + + onSettingsChange() { + } + + loadSettings(settings) { + // Pupulate Provider UI given input settings + if (Object.keys(settings).length == 0) { + console.info("Using default TTS Provider settings") + } + + // Only accept keys defined in defaultSettings + this.settings = this.defaultSettings + + for (const key in settings) { + if (key in this.settings) { + this.settings[key] = settings[key] + } else { + throw `Invalid setting passed to TTS Provider: ${key}` + } + } + + console.info("Settings loaded") + } + + + async onApplyClick() { + return + } + + //#################// + // TTS Interfaces // + //#################// + + async getVoice(voiceName) { + if (this.voices.length == 0) { + this.voices = await this.fetchTtsVoiceIds() + } + const match = this.voices.filter( + voice => voice.name == voiceName + )[0] + if (!match) { + throw `TTS Voice name ${voiceName} not found` + } + return match + } + + async generateTts(text, voiceId) { + const response = await this.fetchTtsGeneration(text, voiceId) + return response + } + + //###########// + // API CALLS // + //###########// + async fetchTtsVoiceIds() { + throwIfModuleMissing() + + const url = new URL(getApiUrl()); + url.pathname = `/api/edge-tts/list` + const response = await doExtrasFetch(url) + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${await response.json()}`) + } + let responseJson = await response.json() + responseJson = responseJson + .sort((a, b) => a.Locale.localeCompare(b.Locale) || a.ShortName.localeCompare(b.ShortName)) + .map(x => ({ name: x.ShortName, voice_id: x.ShortName, preview_url: false, lang: x.Locale })); + return responseJson + } + + + async previewTtsVoice(id) { + this.audioElement.pause(); + this.audioElement.currentTime = 0; + const voice = await this.getVoice(id); + const text = getPreviewString(voice.lang); + const response = await this.fetchTtsGeneration(text, id) + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${await response.json()}`) + } + + const audio = await response.blob(); + const url = URL.createObjectURL(audio); + this.audioElement.src = url; + this.audioElement.play(); + } + + async fetchTtsGeneration(inputText, voiceId) { + throwIfModuleMissing() + + console.info(`Generating new TTS for voice_id ${voiceId}`) + const url = new URL(getApiUrl()); + url.pathname = `/api/edge-tts/generate`; + const response = await doExtrasFetch(url, + { + method: 'POST', + headers: getRequestHeaders(), + body: JSON.stringify({ + "text": inputText, + "voice": voiceId, + }) + } + ) + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${await response.json()}`) + } + return response + } +} +function throwIfModuleMissing() { + if (!modules.includes('edge-tts')) { + + toastr.error(`Edge TTS module not loaded. Add edge-tts to enable-modules and restart the Extras API.`) + throw new Error(`Edge TTS module not loaded.`) + } +} + diff --git a/public/scripts/extensions/tts/index.js b/public/scripts/extensions/tts/index.js index e8a80ecf0..035d485c3 100644 --- a/public/scripts/extensions/tts/index.js +++ b/public/scripts/extensions/tts/index.js @@ -1,6 +1,7 @@ import { callPopup, cancelTtsPlay, eventSource, event_types, isMultigenEnabled, is_send_press, saveSettingsDebounced } from '../../../script.js' import { ModuleWorkerWrapper, extension_settings, getContext } from '../../extensions.js' import { getStringHash } from '../../utils.js' +import { EdgeTtsProvider } from './edge.js' import { ElevenLabsTtsProvider } from './elevenlabs.js' import { SileroTtsProvider } from './silerotts.js' import { SystemTtsProvider } from './system.js' @@ -60,6 +61,7 @@ let ttsProviders = { ElevenLabs: ElevenLabsTtsProvider, Silero: SileroTtsProvider, System: SystemTtsProvider, + Edge: EdgeTtsProvider, } let ttsProvider let ttsProviderName