Blame Raw
· · · 140 lines (4.1 KB)
0 contributors
1import { getRequestHeaders } from '../../../script.js';
2import { splitRecursive } from '../../utils.js';
3import { getPreviewString, saveTtsProviderSettings } from './index.js';
4export { GoogleTranslateTtsProvider };
5
6class GoogleTranslateTtsProvider {
7 settings;
8 voices = [];
9 separator = ' . ';
10 audioElement = document.createElement('audio');
11
12 defaultSettings = {
13 region: '',
14 voiceMap: {},
15 };
16
17 get settingsHtml() {
18 return '';
19 }
20
21 onSettingsChange() {
22 this.voices = [];
23 saveTtsProviderSettings();
24 }
25
26 async loadSettings(settings) {
27 // Populate Provider UI given input settings
28 if (Object.keys(settings).length == 0) {
29 console.info('Using default TTS Provider settings');
30 }
31
32 // Only accept keys defined in defaultSettings
33 this.settings = this.defaultSettings;
34
35 for (const key in settings) {
36 if (key in this.settings) {
37 this.settings[key] = settings[key];
38 } else {
39 throw `Invalid setting passed to TTS Provider: ${key}`;
40 }
41 }
42
43 try {
44 await this.checkReady();
45 console.debug('Google Translate TTS: Settings loaded');
46 } catch {
47 console.debug('Google Translate TTS: Settings loaded, but not ready');
48 }
49 }
50
51 // Perform a simple readiness check by trying to fetch voiceIds
52 async checkReady() {
53 await this.fetchTtsVoiceObjects();
54 }
55
56 async onRefreshClick() {
57 await this.checkReady();
58 }
59
60 //#################//
61 // TTS Interfaces //
62 //#################//
63
64 async getVoice(voiceName) {
65 if (this.voices.length == 0) {
66 this.voices = await this.fetchTtsVoiceObjects();
67 }
68 const match = this.voices.filter(
69 voice => voice.name == voiceName || voice.voice_id == voiceName,
70 )[0];
71 if (!match) {
72 throw `TTS Voice name ${voiceName} not found`;
73 }
74 return match;
75 }
76
77 async generateTts(text, voiceId) {
78 const response = await this.fetchTtsGeneration(text, voiceId);
79 return response;
80 }
81
82 //###########//
83 // API CALLS //
84 //###########//
85 async fetchTtsVoiceObjects() {
86 const response = await fetch('/api/google/list-voices', {
87 method: 'POST',
88 headers: getRequestHeaders(),
89 body: JSON.stringify({}),
90 });
91
92 if (!response.ok) {
93 throw new Error(`HTTP ${response.status}: ${await response.text()}`);
94 }
95 let responseJson = await response.json();
96 responseJson = Object.entries(responseJson)
97 .sort((a, b) => a[1].localeCompare(b[1]))
98 .map(x => ({ name: x[1], voice_id: x[0], preview_url: false, lang: x[0] }));
99 return responseJson;
100 }
101
102 /**
103 * Preview TTS for a given voice ID.
104 * @param {string} id Voice ID
105 */
106 async previewTtsVoice(id) {
107 this.audioElement.pause();
108 this.audioElement.currentTime = 0;
109 const voice = await this.getVoice(id);
110 const text = getPreviewString(voice.lang);
111 const response = await this.fetchTtsGeneration(text, id);
112 if (!response.ok) {
113 throw new Error(`HTTP ${response.status}: ${await response.text()}`);
114 }
115
116 const audio = await response.blob();
117 const url = URL.createObjectURL(audio);
118 this.audioElement.src = url;
119 this.audioElement.play();
120 this.audioElement.onended = () => URL.revokeObjectURL(url);
121 }
122
123 async fetchTtsGeneration(text, voiceId) {
124 const response = await fetch('/api/google/generate-voice', {
125 method: 'POST',
126 headers: getRequestHeaders(),
127 body: JSON.stringify({
128 text: splitRecursive(text, 200),
129 voice: voiceId,
130 }),
131 });
132
133 if (!response.ok) {
134 toastr.error(response.statusText, 'TTS Generation Failed');
135 throw new Error(`HTTP ${response.status}: ${await response.text()}`);
136 }
137
138 return response;
139 }
140}