@andresaya/edge-tts 1.5.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +123 -0
- package/dist/browser/edge-tts.esm.js +383 -0
- package/dist/browser/edge-tts.esm.js.map +7 -0
- package/dist/browser/edge-tts.esm.min.js +24 -0
- package/dist/browser/edge-tts.esm.min.js.map +7 -0
- package/dist/browser/edge-tts.umd.js +407 -0
- package/dist/browser/edge-tts.umd.js.map +7 -0
- package/dist/browser/edge-tts.umd.min.js +25 -0
- package/dist/browser/edge-tts.umd.min.js.map +7 -0
- package/dist/browser.d.ts +1 -0
- package/dist/browser.js +5 -0
- package/dist/cli/edge-tts.js +1 -1
- package/dist/services/EdgeTTS.js +1 -0
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -314,6 +314,129 @@ async function exploreVoices() {
|
|
|
314
314
|
exploreVoices().catch(console.error);
|
|
315
315
|
```
|
|
316
316
|
|
|
317
|
+
## Browser Support
|
|
318
|
+
|
|
319
|
+
This library can be used directly in web browsers via CDN or ES modules.
|
|
320
|
+
|
|
321
|
+
**⚠️ Important:** Currently, this library only works reliably with **Microsoft Edge browser**. We are working to extend support to other browsers. Community contributions and suggestions are welcome!
|
|
322
|
+
|
|
323
|
+
### CDN Usage (UMD)
|
|
324
|
+
|
|
325
|
+
```html
|
|
326
|
+
<!-- Load from CDN -->
|
|
327
|
+
<script src="https://unpkg.com/@andresaya/edge-tts@latest/dist/browser/edge-tts.umd.min.js"></script>
|
|
328
|
+
|
|
329
|
+
<script>
|
|
330
|
+
const tts = new EdgeTTS();
|
|
331
|
+
|
|
332
|
+
// Get available voices
|
|
333
|
+
tts.getVoices().then(voices => {
|
|
334
|
+
console.log('Available voices:', voices.length);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// Synthesize speech
|
|
338
|
+
async function speak() {
|
|
339
|
+
await tts.synthesize("Hello from the browser!", 'en-US-AriaNeural');
|
|
340
|
+
const audioData = tts.getAudioData();
|
|
341
|
+
|
|
342
|
+
// Play audio
|
|
343
|
+
const audioBlob = new Blob([audioData], { type: 'audio/mp3' });
|
|
344
|
+
const audioUrl = URL.createObjectURL(audioBlob);
|
|
345
|
+
const audio = new Audio(audioUrl);
|
|
346
|
+
audio.play();
|
|
347
|
+
}
|
|
348
|
+
</script>
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### ES Module Import
|
|
352
|
+
|
|
353
|
+
```html
|
|
354
|
+
<script type="module">
|
|
355
|
+
import { EdgeTTS } from 'https://unpkg.com/@andresaya/edge-tts@latest/dist/browser/edge-tts.esm.min.js';
|
|
356
|
+
|
|
357
|
+
const tts = new EdgeTTS();
|
|
358
|
+
|
|
359
|
+
// Use the library
|
|
360
|
+
const voices = await tts.getVoices();
|
|
361
|
+
console.log(voices);
|
|
362
|
+
</script>
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Custom SSML Support in Browser
|
|
366
|
+
|
|
367
|
+
The browser version supports custom SSML (Speech Synthesis Markup Language) for advanced speech control:
|
|
368
|
+
|
|
369
|
+
```html
|
|
370
|
+
<script src="https://unpkg.com/@andresaya/edge-tts@latest/dist/browser/edge-tts.umd.min.js"></script>
|
|
371
|
+
|
|
372
|
+
<script>
|
|
373
|
+
const tts = new EdgeTTS();
|
|
374
|
+
|
|
375
|
+
// Custom SSML with emphasis, breaks, and expression
|
|
376
|
+
const ssml = `
|
|
377
|
+
<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis"
|
|
378
|
+
xmlns:mstts="https://www.w3.org/2001/mstts" xml:lang="en-US">
|
|
379
|
+
<voice name="en-US-AriaNeural">
|
|
380
|
+
<mstts:express-as style="cheerful">
|
|
381
|
+
<prosody rate="+10%" pitch="+5Hz">
|
|
382
|
+
Hello! This is <emphasis>custom SSML</emphasis>.
|
|
383
|
+
<break time="500ms"/>
|
|
384
|
+
You have full control over speech synthesis!
|
|
385
|
+
</prosody>
|
|
386
|
+
</mstts:express-as>
|
|
387
|
+
</voice>
|
|
388
|
+
</speak>
|
|
389
|
+
`;
|
|
390
|
+
|
|
391
|
+
// Synthesize with SSML
|
|
392
|
+
async function speakSSML() {
|
|
393
|
+
await tts.synthesize(ssml, '', { inputType: 'ssml' });
|
|
394
|
+
const audioData = tts.getAudioData();
|
|
395
|
+
|
|
396
|
+
const audioBlob = new Blob([audioData], { type: 'audio/mp3' });
|
|
397
|
+
const audioUrl = URL.createObjectURL(audioBlob);
|
|
398
|
+
const audio = new Audio(audioUrl);
|
|
399
|
+
audio.play();
|
|
400
|
+
}
|
|
401
|
+
</script>
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### Streaming Support in Browser
|
|
405
|
+
|
|
406
|
+
```html
|
|
407
|
+
<script type="module">
|
|
408
|
+
import { EdgeTTS } from 'https://unpkg.com/@andresaya/edge-tts@latest/dist/browser/edge-tts.esm.min.js';
|
|
409
|
+
|
|
410
|
+
const tts = new EdgeTTS();
|
|
411
|
+
const chunks = [];
|
|
412
|
+
|
|
413
|
+
// Stream audio chunks in real-time
|
|
414
|
+
for await (const chunk of tts.synthesizeStream("Long text to stream...", 'en-US-AriaNeural')) {
|
|
415
|
+
chunks.push(chunk);
|
|
416
|
+
console.log(`Received chunk: ${chunk.length} bytes`);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Combine and play all chunks
|
|
420
|
+
const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
|
|
421
|
+
const audioData = new Uint8Array(totalLength);
|
|
422
|
+
let offset = 0;
|
|
423
|
+
for (const chunk of chunks) {
|
|
424
|
+
audioData.set(chunk, offset);
|
|
425
|
+
offset += chunk.length;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const blob = new Blob([audioData], { type: 'audio/mp3' });
|
|
429
|
+
const audio = new Audio(URL.createObjectURL(blob));
|
|
430
|
+
audio.play();
|
|
431
|
+
</script>
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### Complete Browser Example
|
|
435
|
+
|
|
436
|
+
For a full working example with voice selection and synthesis, see [`examples/browser-standalone.html`](examples/browser-standalone.html).
|
|
437
|
+
|
|
438
|
+
For advanced SSML examples, see [`examples/browser-ssml-demo.html`](examples/browser-ssml-demo.html).
|
|
439
|
+
|
|
317
440
|
## Voice Options
|
|
318
441
|
|
|
319
442
|
### Synthesis Parameters
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
|
|
5
|
+
// src/config/constants.ts
|
|
6
|
+
var Constants = {
|
|
7
|
+
TRUSTED_CLIENT_TOKEN: "6A5AA1D4EAFF4E9FB37E23D68491D6F4",
|
|
8
|
+
BASE_URL: "https://api.msedgeservices.com/tts/cognitiveservices",
|
|
9
|
+
WSS_URL: "wss://api.msedgeservices.com/tts/cognitiveservices/websocket/v1",
|
|
10
|
+
VOICES_URL: "https://api.msedgeservices.com/tts/cognitiveservices/voices/list",
|
|
11
|
+
CHROMIUM_FULL_VERSION: "142.0.3595.0",
|
|
12
|
+
CHROMIUM_MAJOR_VERSION: "142",
|
|
13
|
+
VERSION_MS_GEC: "1-142.0.3595",
|
|
14
|
+
token32() {
|
|
15
|
+
const bytes = new Uint8Array(16);
|
|
16
|
+
crypto.getRandomValues(bytes);
|
|
17
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("").toUpperCase();
|
|
18
|
+
},
|
|
19
|
+
getBaseHeaders() {
|
|
20
|
+
return {
|
|
21
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0",
|
|
22
|
+
"Accept-Encoding": "gzip, deflate, br, zstd",
|
|
23
|
+
"Accept-Language": "es,es-ES;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,es-CO;q=0.5,es-MX;q=0.4",
|
|
24
|
+
"Cookie": "MUID=" + this.token32()
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
WSS_HEADERS: {
|
|
28
|
+
"Pragma": "no-cache",
|
|
29
|
+
"Cache-Control": "no-cache",
|
|
30
|
+
"Origin": "chrome-extension://jdiccldimpdaibmpdkjnbmckianbfold",
|
|
31
|
+
"Sec-WebSocket-Protocol": "synthesize",
|
|
32
|
+
"Sec-WebSocket-Version": "13",
|
|
33
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0"
|
|
34
|
+
},
|
|
35
|
+
VOICE_HEADERS: {
|
|
36
|
+
"Sec-CH-UA": '" Not;A Brand";v="99", "Microsoft Edge";v="140", "Chromium";v="140"',
|
|
37
|
+
"Sec-CH-UA-Mobile": "?0",
|
|
38
|
+
"Accept": "*/*",
|
|
39
|
+
"Sec-Fetch-Site": "none",
|
|
40
|
+
"Sec-Fetch-Mode": "cors",
|
|
41
|
+
"Sec-Fetch-Dest": "empty"
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// src/browser/EdgeTTS.browser.ts
|
|
46
|
+
var EdgeTTS = class {
|
|
47
|
+
constructor() {
|
|
48
|
+
__publicField(this, "audio_stream", []);
|
|
49
|
+
__publicField(this, "ws");
|
|
50
|
+
}
|
|
51
|
+
async getVoices() {
|
|
52
|
+
const secMsGEC = await this.generateSecMsGec(Constants.TRUSTED_CLIENT_TOKEN);
|
|
53
|
+
const response = await fetch(
|
|
54
|
+
`${Constants.VOICES_URL}?Ocp-Apim-Subscription-Key=${Constants.TRUSTED_CLIENT_TOKEN}&Sec-MS-GEC=${secMsGEC}&Sec-MS-GEC-Version=${Constants.VERSION_MS_GEC}`,
|
|
55
|
+
{ headers: Constants.getBaseHeaders() }
|
|
56
|
+
);
|
|
57
|
+
const data = await response.json();
|
|
58
|
+
return data.map((voice) => {
|
|
59
|
+
voice.FriendlyName = voice.FriendlyName || voice.LocalName;
|
|
60
|
+
delete voice.SampleRateHertz;
|
|
61
|
+
delete voice.Status;
|
|
62
|
+
return voice;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
async getVoicesByLanguage(locale) {
|
|
66
|
+
const voices = await this.getVoices();
|
|
67
|
+
return voices.filter((voice) => voice.Locale.startsWith(locale));
|
|
68
|
+
}
|
|
69
|
+
async getVoicesByGender(gender) {
|
|
70
|
+
const voices = await this.getVoices();
|
|
71
|
+
return voices.filter((voice) => voice.Gender === gender);
|
|
72
|
+
}
|
|
73
|
+
generateUUID() {
|
|
74
|
+
return "xxxxxxxx-xxxx-xxxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
|
|
75
|
+
const r = Math.random() * 16 | 0;
|
|
76
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
77
|
+
return v.toString(16);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
validatePitch(pitch) {
|
|
81
|
+
if (typeof pitch === "number") {
|
|
82
|
+
return pitch >= 0 ? `+${pitch}Hz` : `${pitch}Hz`;
|
|
83
|
+
}
|
|
84
|
+
return pitch;
|
|
85
|
+
}
|
|
86
|
+
validateRate(rate) {
|
|
87
|
+
let rateValue;
|
|
88
|
+
if (typeof rate === "string") {
|
|
89
|
+
rateValue = parseFloat(rate.replace("%", ""));
|
|
90
|
+
} else {
|
|
91
|
+
rateValue = rate;
|
|
92
|
+
}
|
|
93
|
+
return rateValue >= 0 ? `+${rateValue}%` : `${rateValue}%`;
|
|
94
|
+
}
|
|
95
|
+
validateVolume(volume) {
|
|
96
|
+
let volumeValue;
|
|
97
|
+
if (typeof volume === "string") {
|
|
98
|
+
volumeValue = parseInt(volume.replace("%", ""), 10);
|
|
99
|
+
} else {
|
|
100
|
+
volumeValue = volume;
|
|
101
|
+
}
|
|
102
|
+
return `${volumeValue}%`;
|
|
103
|
+
}
|
|
104
|
+
detectSSML(content) {
|
|
105
|
+
const trimmedContent = content.trim();
|
|
106
|
+
const looksLikeSSML = /^<\?xml|^<speak/i.test(trimmedContent);
|
|
107
|
+
if (!looksLikeSSML) {
|
|
108
|
+
return { isValid: true, isSSML: false };
|
|
109
|
+
}
|
|
110
|
+
const errors = [];
|
|
111
|
+
const hasSpeakTag = /<speak\b[^>]*>[\s\S]*<\/speak>/i.test(trimmedContent);
|
|
112
|
+
const hasVoiceTag = /<voice\b[^>]*>[\s\S]*<\/voice>/i.test(trimmedContent);
|
|
113
|
+
if (!hasSpeakTag) {
|
|
114
|
+
throw new Error("Invalid SSML: Missing <speak> tag");
|
|
115
|
+
}
|
|
116
|
+
if (!hasVoiceTag) {
|
|
117
|
+
throw new Error("Invalid SSML: Missing <voice> tag");
|
|
118
|
+
}
|
|
119
|
+
const hasCorrectNamespace = /xmlns="http:\/\/www\.w3\.org\/2001\/10\/synthesis"/i.test(trimmedContent);
|
|
120
|
+
if (!hasCorrectNamespace && hasSpeakTag) {
|
|
121
|
+
throw new Error("Invalid SSML: Missing or incorrect namespace declaration");
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
isValid: errors.length === 0,
|
|
125
|
+
isSSML: hasSpeakTag || hasVoiceTag,
|
|
126
|
+
errors: errors.length > 0 ? errors : void 0
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
escapeXML(text) {
|
|
130
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
131
|
+
}
|
|
132
|
+
getSSML(content, voice, options = {}) {
|
|
133
|
+
const inputType = options.inputType || "auto";
|
|
134
|
+
let treatAsSSML = false;
|
|
135
|
+
if (inputType === "ssml") {
|
|
136
|
+
treatAsSSML = true;
|
|
137
|
+
} else if (inputType === "text") {
|
|
138
|
+
treatAsSSML = false;
|
|
139
|
+
} else {
|
|
140
|
+
const detection = this.detectSSML(content);
|
|
141
|
+
treatAsSSML = detection.isSSML;
|
|
142
|
+
if (detection.isSSML) {
|
|
143
|
+
console.log("\u2192 Detected SSML input");
|
|
144
|
+
if (!detection.isValid) {
|
|
145
|
+
console.warn("\u26A0 SSML validation warnings:", detection.errors);
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
console.log("\u2192 Detected plain text input");
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (treatAsSSML) {
|
|
152
|
+
let ssml = content.trim();
|
|
153
|
+
if (!ssml.includes("xmlns=")) {
|
|
154
|
+
ssml = ssml.replace(
|
|
155
|
+
/<speak([^>]*)>/i,
|
|
156
|
+
'<speak$1 xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="https://www.w3.org/2001/mstts">'
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
if (!/<voice\b[^>]*>/i.test(ssml) && voice) {
|
|
160
|
+
ssml = ssml.replace(
|
|
161
|
+
/(<speak[^>]*>)([\s\S]*?)(<\/speak>)/i,
|
|
162
|
+
`$1<voice name="${voice}">$2</voice>$3`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
return ssml;
|
|
166
|
+
}
|
|
167
|
+
const pitch = this.validatePitch(options.pitch ?? 0);
|
|
168
|
+
const rate = this.validateRate(options.rate ?? 0);
|
|
169
|
+
const volume = this.validateVolume(options.volume ?? 0);
|
|
170
|
+
const escapedText = this.escapeXML(content);
|
|
171
|
+
return `<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="https://www.w3.org/2001/mstts" xml:lang="en-US">
|
|
172
|
+
<voice name="${voice}">
|
|
173
|
+
<prosody pitch="${pitch}" rate="${rate}" volume="${volume}">
|
|
174
|
+
${escapedText}
|
|
175
|
+
</prosody>
|
|
176
|
+
</voice>
|
|
177
|
+
</speak>`;
|
|
178
|
+
}
|
|
179
|
+
nowRFC1123() {
|
|
180
|
+
return (/* @__PURE__ */ new Date()).toUTCString();
|
|
181
|
+
}
|
|
182
|
+
parseRFC1123(rfcStr) {
|
|
183
|
+
return new Date(rfcStr);
|
|
184
|
+
}
|
|
185
|
+
buildTTSConfigMessage() {
|
|
186
|
+
const timestamp = this.nowRFC1123();
|
|
187
|
+
return `X-Timestamp:${timestamp}\r
|
|
188
|
+
Content-Type:application/json; charset=utf-8\r
|
|
189
|
+
Path:speech.config\r
|
|
190
|
+
\r
|
|
191
|
+
{"context":{"synthesis":{"audio":{"metadataoptions":{"sentenceBoundaryEnabled":false,"wordBoundaryEnabled":true},"outputFormat":"audio-24khz-48kbitrate-mono-mp3"}}}}`;
|
|
192
|
+
}
|
|
193
|
+
async synthesize(text, voice = "en-US-JennyNeural", options = {}) {
|
|
194
|
+
const secMsGEC = await this.generateSecMsGec(Constants.TRUSTED_CLIENT_TOKEN);
|
|
195
|
+
return new Promise((resolve, reject) => {
|
|
196
|
+
this.audio_stream = [];
|
|
197
|
+
const reqId = this.generateUUID();
|
|
198
|
+
const url = `${Constants.WSS_URL}?Ocp-Apim-Subscription-Key=${Constants.TRUSTED_CLIENT_TOKEN}&Sec-MS-GEC=${secMsGEC}&Sec-MS-GEC-Version=${Constants.VERSION_MS_GEC}&ConnectionId=${reqId}`;
|
|
199
|
+
console.log("WebSocket URL:", url);
|
|
200
|
+
this.ws = new WebSocket(url);
|
|
201
|
+
const SSML_text = this.getSSML(text, voice, options);
|
|
202
|
+
const timeout = setTimeout(() => {
|
|
203
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
204
|
+
this.ws.close();
|
|
205
|
+
}
|
|
206
|
+
reject(new Error("Synthesis timeout"));
|
|
207
|
+
}, 3e4);
|
|
208
|
+
this.ws.onopen = () => {
|
|
209
|
+
const message = this.buildTTSConfigMessage();
|
|
210
|
+
this.ws.send(message);
|
|
211
|
+
const timestamp = this.nowRFC1123();
|
|
212
|
+
const speechMessage = `X-RequestId:${reqId}\r
|
|
213
|
+
Content-Type:application/ssml+xml\r
|
|
214
|
+
X-Timestamp:${timestamp}\r
|
|
215
|
+
Path:ssml\r
|
|
216
|
+
\r
|
|
217
|
+
${SSML_text}`;
|
|
218
|
+
this.ws.send(speechMessage);
|
|
219
|
+
};
|
|
220
|
+
this.ws.onmessage = (event) => {
|
|
221
|
+
if (typeof event.data === "string") {
|
|
222
|
+
if (event.data.includes("Path:turn.end")) {
|
|
223
|
+
this.ws?.close();
|
|
224
|
+
}
|
|
225
|
+
} else {
|
|
226
|
+
const reader = new FileReader();
|
|
227
|
+
reader.onload = () => {
|
|
228
|
+
const arrayBuffer = reader.result;
|
|
229
|
+
const uint8Array = new Uint8Array(arrayBuffer);
|
|
230
|
+
this.processAudioData(uint8Array);
|
|
231
|
+
};
|
|
232
|
+
reader.readAsArrayBuffer(event.data);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
this.ws.onerror = (err) => {
|
|
236
|
+
clearTimeout(timeout);
|
|
237
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
238
|
+
this.ws.close();
|
|
239
|
+
}
|
|
240
|
+
reject(new Error("WebSocket error"));
|
|
241
|
+
};
|
|
242
|
+
this.ws.onclose = () => {
|
|
243
|
+
clearTimeout(timeout);
|
|
244
|
+
resolve();
|
|
245
|
+
};
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
async *synthesizeStream(text, voice = "en-US-JennyNeural", options = {}) {
|
|
249
|
+
this.audio_stream = [];
|
|
250
|
+
const reqId = this.generateUUID();
|
|
251
|
+
const secMsGEC = await this.generateSecMsGec(Constants.TRUSTED_CLIENT_TOKEN);
|
|
252
|
+
const url = `${Constants.WSS_URL}?Ocp-Apim-Subscription-Key=${Constants.TRUSTED_CLIENT_TOKEN}&Sec-MS-GEC=${secMsGEC}&Sec-MS-GEC-Version=${Constants.VERSION_MS_GEC}&ConnectionId=${reqId}`;
|
|
253
|
+
this.ws = new WebSocket(url);
|
|
254
|
+
const SSML_text = this.getSSML(text, voice, options);
|
|
255
|
+
const queue = [];
|
|
256
|
+
let done = false;
|
|
257
|
+
let error = null;
|
|
258
|
+
let notify = null;
|
|
259
|
+
const push = (chunk) => {
|
|
260
|
+
queue.push(chunk);
|
|
261
|
+
if (notify) {
|
|
262
|
+
notify();
|
|
263
|
+
notify = null;
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
const timeout = setTimeout(() => {
|
|
267
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
268
|
+
this.ws.close();
|
|
269
|
+
}
|
|
270
|
+
}, 3e4);
|
|
271
|
+
this.ws.onopen = () => {
|
|
272
|
+
const message = this.buildTTSConfigMessage();
|
|
273
|
+
this.ws.send(message);
|
|
274
|
+
const timestamp = this.nowRFC1123();
|
|
275
|
+
const speechMessage = `X-RequestId:${reqId}\r
|
|
276
|
+
Content-Type:application/ssml+xml\r
|
|
277
|
+
X-Timestamp:${timestamp}\r
|
|
278
|
+
Path:ssml\r
|
|
279
|
+
\r
|
|
280
|
+
${SSML_text}`;
|
|
281
|
+
this.ws.send(speechMessage);
|
|
282
|
+
};
|
|
283
|
+
this.ws.onmessage = (event) => {
|
|
284
|
+
if (typeof event.data === "string") {
|
|
285
|
+
if (event.data.includes("Path:turn.end")) {
|
|
286
|
+
this.ws?.close();
|
|
287
|
+
}
|
|
288
|
+
} else {
|
|
289
|
+
const reader = new FileReader();
|
|
290
|
+
reader.onload = () => {
|
|
291
|
+
const arrayBuffer = reader.result;
|
|
292
|
+
const uint8Array = new Uint8Array(arrayBuffer);
|
|
293
|
+
const needle = new TextEncoder().encode("Path:audio\r\n");
|
|
294
|
+
const audioStartIndex = this.indexOf(uint8Array, needle);
|
|
295
|
+
if (audioStartIndex !== -1) {
|
|
296
|
+
const audioChunk = uint8Array.slice(audioStartIndex + needle.length);
|
|
297
|
+
this.audio_stream.push(audioChunk);
|
|
298
|
+
push(audioChunk);
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
reader.readAsArrayBuffer(event.data);
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
this.ws.onerror = (err) => {
|
|
305
|
+
error = new Error("WebSocket error");
|
|
306
|
+
done = true;
|
|
307
|
+
if (notify) {
|
|
308
|
+
notify();
|
|
309
|
+
notify = null;
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
this.ws.onclose = () => {
|
|
313
|
+
clearTimeout(timeout);
|
|
314
|
+
done = true;
|
|
315
|
+
if (notify) {
|
|
316
|
+
notify();
|
|
317
|
+
notify = null;
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
while (!done || queue.length > 0) {
|
|
321
|
+
if (queue.length === 0) {
|
|
322
|
+
await new Promise((resolve) => notify = resolve);
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
const chunk = queue.shift();
|
|
326
|
+
if (chunk) {
|
|
327
|
+
yield chunk;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (error) {
|
|
331
|
+
throw error;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
processAudioData(buffer) {
|
|
335
|
+
const needle = new TextEncoder().encode("Path:audio\r\n");
|
|
336
|
+
const audioStartIndex = this.indexOf(buffer, needle);
|
|
337
|
+
if (audioStartIndex !== -1) {
|
|
338
|
+
const audioChunk = buffer.slice(audioStartIndex + needle.length);
|
|
339
|
+
this.audio_stream.push(audioChunk);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
indexOf(arr, subarr) {
|
|
343
|
+
for (let i = 0; i <= arr.length - subarr.length; i++) {
|
|
344
|
+
let match = true;
|
|
345
|
+
for (let j = 0; j < subarr.length; j++) {
|
|
346
|
+
if (arr[i + j] !== subarr[j]) {
|
|
347
|
+
match = false;
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
if (match) return i;
|
|
352
|
+
}
|
|
353
|
+
return -1;
|
|
354
|
+
}
|
|
355
|
+
async generateSecMsGec(trustedClientToken) {
|
|
356
|
+
const now = this.nowRFC1123();
|
|
357
|
+
const fixedDate = this.parseRFC1123(now);
|
|
358
|
+
const ticks = Math.floor(fixedDate.getTime() / 1e3) + 11644473600;
|
|
359
|
+
const rounded = ticks - ticks % 300;
|
|
360
|
+
const windowsTicks = rounded * 1e7;
|
|
361
|
+
const encoder = new TextEncoder();
|
|
362
|
+
const data = encoder.encode(`${windowsTicks}${trustedClientToken}`);
|
|
363
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
364
|
+
return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("").toUpperCase();
|
|
365
|
+
}
|
|
366
|
+
getAudioData() {
|
|
367
|
+
if (this.audio_stream.length === 0) {
|
|
368
|
+
throw new Error("No audio data available");
|
|
369
|
+
}
|
|
370
|
+
const totalLength = this.audio_stream.reduce((acc, chunk) => acc + chunk.length, 0);
|
|
371
|
+
const result = new Uint8Array(totalLength);
|
|
372
|
+
let offset = 0;
|
|
373
|
+
for (const chunk of this.audio_stream) {
|
|
374
|
+
result.set(chunk, offset);
|
|
375
|
+
offset += chunk.length;
|
|
376
|
+
}
|
|
377
|
+
return result;
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
export {
|
|
381
|
+
EdgeTTS
|
|
382
|
+
};
|
|
383
|
+
//# sourceMappingURL=edge-tts.esm.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/config/constants.ts", "../../src/browser/EdgeTTS.browser.ts"],
|
|
4
|
+
"sourcesContent": ["export const Constants = {\r\n TRUSTED_CLIENT_TOKEN: '6A5AA1D4EAFF4E9FB37E23D68491D6F4',\r\n BASE_URL: 'https://api.msedgeservices.com/tts/cognitiveservices',\r\n WSS_URL: 'wss://api.msedgeservices.com/tts/cognitiveservices/websocket/v1',\r\n VOICES_URL: 'https://api.msedgeservices.com/tts/cognitiveservices/voices/list',\r\n \r\n CHROMIUM_FULL_VERSION: '142.0.3595.0',\r\n CHROMIUM_MAJOR_VERSION: '142',\r\n VERSION_MS_GEC: '1-142.0.3595',\r\n \r\n token32() {\r\n const bytes = new Uint8Array(16);\r\n crypto.getRandomValues(bytes);\r\n \r\n return Array.from(bytes)\r\n .map(b => b.toString(16).padStart(2, '0'))\r\n .join('')\r\n .toUpperCase();\r\n },\r\n \r\n getBaseHeaders() {\r\n return {\r\n 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0',\r\n 'Accept-Encoding': 'gzip, deflate, br, zstd',\r\n 'Accept-Language': 'es,es-ES;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,es-CO;q=0.5,es-MX;q=0.4',\r\n 'Cookie': 'MUID=' + this.token32()\r\n };\r\n },\r\n \r\n WSS_HEADERS: {\r\n 'Pragma': 'no-cache',\r\n 'Cache-Control': 'no-cache',\r\n 'Origin': 'chrome-extension://jdiccldimpdaibmpdkjnbmckianbfold',\r\n 'Sec-WebSocket-Protocol': 'synthesize',\r\n 'Sec-WebSocket-Version': '13',\r\n 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0'\r\n },\r\n \r\n VOICE_HEADERS: {\r\n 'Sec-CH-UA': '\" Not;A Brand\";v=\"99\", \"Microsoft Edge\";v=\"140\", \"Chromium\";v=\"140\"',\r\n 'Sec-CH-UA-Mobile': '?0',\r\n 'Accept': '*/*',\r\n 'Sec-Fetch-Site': 'none',\r\n 'Sec-Fetch-Mode': 'cors',\r\n 'Sec-Fetch-Dest': 'empty'\r\n }\r\n};\r\n", "import { Constants } from '../config/constants';\r\n\r\nexport interface Voice {\r\n Name: string;\r\n ShortName: string;\r\n Gender: string;\r\n Locale: string;\r\n FriendlyName: string;\r\n LocalName: string;\r\n}\r\n\r\nexport interface SynthesisOptions {\r\n pitch?: string | number;\r\n rate?: string | number;\r\n volume?: string | number;\r\n inputType?: 'auto' | 'ssml' | 'text';\r\n}\r\n\r\ninterface SSMLValidationResult {\r\n isValid: boolean;\r\n isSSML: boolean;\r\n errors?: string[];\r\n}\r\n\r\nexport class EdgeTTS {\r\n private audio_stream: Uint8Array[] = [];\r\n private ws?: WebSocket;\r\n\r\n async getVoices(): Promise<Voice[]> {\r\n const secMsGEC = await this.generateSecMsGec(Constants.TRUSTED_CLIENT_TOKEN);\r\n \r\n const response = await fetch(\r\n `${Constants.VOICES_URL}?Ocp-Apim-Subscription-Key=${Constants.TRUSTED_CLIENT_TOKEN}&Sec-MS-GEC=${secMsGEC}&Sec-MS-GEC-Version=${Constants.VERSION_MS_GEC}`,\r\n { headers: Constants.getBaseHeaders() }\r\n );\r\n \r\n const data = await response.json();\r\n return data.map((voice: any) => {\r\n voice.FriendlyName = voice.FriendlyName || voice.LocalName;\r\n delete voice.SampleRateHertz;\r\n delete voice.Status;\r\n return voice;\r\n });\r\n }\r\n\r\n async getVoicesByLanguage(locale: string): Promise<Voice[]> {\r\n const voices = await this.getVoices();\r\n return voices.filter(voice => voice.Locale.startsWith(locale));\r\n }\r\n\r\n async getVoicesByGender(gender: 'Male' | 'Female'): Promise<Voice[]> {\r\n const voices = await this.getVoices();\r\n return voices.filter(voice => voice.Gender === gender);\r\n }\r\n\r\n private generateUUID(): string {\r\n return 'xxxxxxxx-xxxx-xxxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {\r\n const r = Math.random() * 16 | 0;\r\n const v = c === 'x' ? r : (r & 0x3 | 0x8);\r\n return v.toString(16);\r\n });\r\n }\r\n\r\n private validatePitch(pitch: string | number): string {\r\n if (typeof pitch === 'number') {\r\n return (pitch >= 0 ? `+${pitch}Hz` : `${pitch}Hz`);\r\n }\r\n return pitch;\r\n }\r\n\r\n private validateRate(rate: string | number): string {\r\n let rateValue: number;\r\n if (typeof rate === 'string') {\r\n rateValue = parseFloat(rate.replace('%', ''));\r\n } else {\r\n rateValue = rate;\r\n }\r\n return rateValue >= 0 ? `+${rateValue}%` : `${rateValue}%`;\r\n }\r\n\r\n private validateVolume(volume: string | number): string {\r\n let volumeValue: number;\r\n if (typeof volume === 'string') {\r\n volumeValue = parseInt(volume.replace('%', ''), 10);\r\n } else {\r\n volumeValue = volume;\r\n }\r\n return `${volumeValue}%`;\r\n }\r\n\r\n private detectSSML(content: string): SSMLValidationResult {\r\n const trimmedContent = content.trim();\r\n const looksLikeSSML = /^<\\?xml|^<speak/i.test(trimmedContent);\r\n\r\n if (!looksLikeSSML) {\r\n return { isValid: true, isSSML: false };\r\n }\r\n\r\n const errors: string[] = [];\r\n const hasSpeakTag = /<speak\\b[^>]*>[\\s\\S]*<\\/speak>/i.test(trimmedContent);\r\n const hasVoiceTag = /<voice\\b[^>]*>[\\s\\S]*<\\/voice>/i.test(trimmedContent);\r\n\r\n if (!hasSpeakTag) {\r\n throw new Error('Invalid SSML: Missing <speak> tag');\r\n }\r\n\r\n if (!hasVoiceTag) {\r\n throw new Error('Invalid SSML: Missing <voice> tag');\r\n }\r\n\r\n const hasCorrectNamespace = /xmlns=\"http:\\/\\/www\\.w3\\.org\\/2001\\/10\\/synthesis\"/i.test(trimmedContent);\r\n if (!hasCorrectNamespace && hasSpeakTag) {\r\n throw new Error('Invalid SSML: Missing or incorrect namespace declaration');\r\n }\r\n\r\n return {\r\n isValid: errors.length === 0,\r\n isSSML: hasSpeakTag || hasVoiceTag,\r\n errors: errors.length > 0 ? errors : undefined\r\n };\r\n }\r\n\r\n private escapeXML(text: string): string {\r\n return text\r\n .replace(/&/g, '&')\r\n .replace(/</g, '<')\r\n .replace(/>/g, '>')\r\n .replace(/\"/g, '"')\r\n .replace(/'/g, ''');\r\n }\r\n\r\n private getSSML(content: string, voice: string, options: SynthesisOptions = {}): string {\r\n const inputType = options.inputType || 'auto';\r\n let treatAsSSML = false;\r\n\r\n if (inputType === 'ssml') {\r\n treatAsSSML = true;\r\n } else if (inputType === 'text') {\r\n treatAsSSML = false;\r\n } else { // 'auto'\r\n const detection = this.detectSSML(content);\r\n treatAsSSML = detection.isSSML;\r\n\r\n if (detection.isSSML) {\r\n console.log('\u2192 Detected SSML input');\r\n if (!detection.isValid) {\r\n console.warn('\u26A0 SSML validation warnings:', detection.errors);\r\n }\r\n } else {\r\n console.log('\u2192 Detected plain text input');\r\n }\r\n }\r\n\r\n if (treatAsSSML) {\r\n let ssml = content.trim();\r\n\r\n if (!ssml.includes('xmlns=')) {\r\n ssml = ssml.replace(\r\n /<speak([^>]*)>/i,\r\n '<speak$1 xmlns=\"http://www.w3.org/2001/10/synthesis\" xmlns:mstts=\"https://www.w3.org/2001/mstts\">'\r\n );\r\n }\r\n\r\n if (!/<voice\\b[^>]*>/i.test(ssml) && voice) {\r\n ssml = ssml.replace(\r\n /(<speak[^>]*>)([\\s\\S]*?)(<\\/speak>)/i,\r\n `$1<voice name=\"${voice}\">$2</voice>$3`\r\n );\r\n }\r\n\r\n return ssml;\r\n }\r\n\r\n const pitch = this.validatePitch(options.pitch ?? 0);\r\n const rate = this.validateRate(options.rate ?? 0);\r\n const volume = this.validateVolume(options.volume ?? 0);\r\n const escapedText = this.escapeXML(content);\r\n\r\n return `<speak version=\"1.0\" xmlns=\"http://www.w3.org/2001/10/synthesis\" xmlns:mstts=\"https://www.w3.org/2001/mstts\" xml:lang=\"en-US\">\r\n <voice name=\"${voice}\">\r\n <prosody pitch=\"${pitch}\" rate=\"${rate}\" volume=\"${volume}\">\r\n ${escapedText}\r\n </prosody>\r\n </voice>\r\n </speak>`;\r\n }\r\n\r\n private nowRFC1123(): string {\r\n return new Date().toUTCString();\r\n }\r\n\r\n private parseRFC1123(rfcStr: string): Date {\r\n return new Date(rfcStr);\r\n }\r\n\r\n private buildTTSConfigMessage(): string {\r\n const timestamp = this.nowRFC1123();\r\n return `X-Timestamp:${timestamp}\\r\\nContent-Type:application/json; charset=utf-8\\r\\nPath:speech.config\\r\\n\\r\\n` +\r\n `{\"context\":{\"synthesis\":{\"audio\":{\"metadataoptions\":{\"sentenceBoundaryEnabled\":false,\"wordBoundaryEnabled\":true},\"outputFormat\":\"audio-24khz-48kbitrate-mono-mp3\"}}}}`;\r\n }\r\n\r\n async synthesize(text: string, voice: string = 'en-US-JennyNeural', options: SynthesisOptions = {}): Promise<void> {\r\n const secMsGEC = await this.generateSecMsGec(Constants.TRUSTED_CLIENT_TOKEN);\r\n\r\n return new Promise((resolve, reject) => {\r\n this.audio_stream = [];\r\n const reqId = this.generateUUID();\r\n const url = `${Constants.WSS_URL}?Ocp-Apim-Subscription-Key=${Constants.TRUSTED_CLIENT_TOKEN}&Sec-MS-GEC=${secMsGEC}&Sec-MS-GEC-Version=${Constants.VERSION_MS_GEC}&ConnectionId=${reqId}`;\r\n \r\n console.log('WebSocket URL:', url);\r\n this.ws = new WebSocket(url);\r\n\r\n const SSML_text = this.getSSML(text, voice, options);\r\n \r\n const timeout = setTimeout(() => {\r\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\r\n this.ws.close();\r\n }\r\n reject(new Error(\"Synthesis timeout\"));\r\n }, 30000);\r\n\r\n this.ws.onopen = () => {\r\n const message = this.buildTTSConfigMessage();\r\n this.ws!.send(message);\r\n const timestamp = this.nowRFC1123();\r\n const speechMessage = `X-RequestId:${reqId}\\r\\nContent-Type:application/ssml+xml\\r\\nX-Timestamp:${timestamp}\\r\\nPath:ssml\\r\\n\\r\\n${SSML_text}`;\r\n this.ws!.send(speechMessage);\r\n };\r\n\r\n this.ws.onmessage = (event) => {\r\n if (typeof event.data === 'string') {\r\n if (event.data.includes('Path:turn.end')) {\r\n this.ws?.close();\r\n }\r\n } else {\r\n const reader = new FileReader();\r\n reader.onload = () => {\r\n const arrayBuffer = reader.result as ArrayBuffer;\r\n const uint8Array = new Uint8Array(arrayBuffer);\r\n this.processAudioData(uint8Array);\r\n };\r\n reader.readAsArrayBuffer(event.data);\r\n }\r\n };\r\n\r\n this.ws.onerror = (err: any) => {\r\n clearTimeout(timeout);\r\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\r\n this.ws.close();\r\n }\r\n reject(new Error('WebSocket error'));\r\n };\r\n\r\n this.ws.onclose = () => {\r\n clearTimeout(timeout);\r\n resolve();\r\n };\r\n });\r\n }\r\n\r\n async *synthesizeStream(text: string, voice: string = 'en-US-JennyNeural', options: SynthesisOptions = {}): AsyncGenerator<Uint8Array, void, unknown> {\r\n this.audio_stream = [];\r\n\r\n const reqId = this.generateUUID();\r\n const secMsGEC = await this.generateSecMsGec(Constants.TRUSTED_CLIENT_TOKEN);\r\n\r\n const url = `${Constants.WSS_URL}?Ocp-Apim-Subscription-Key=${Constants.TRUSTED_CLIENT_TOKEN}&Sec-MS-GEC=${secMsGEC}&Sec-MS-GEC-Version=${Constants.VERSION_MS_GEC}&ConnectionId=${reqId}`;\r\n\r\n this.ws = new WebSocket(url);\r\n\r\n const SSML_text = this.getSSML(text, voice, options);\r\n\r\n const queue: Uint8Array[] = [];\r\n let done = false;\r\n let error: Error | null = null;\r\n let notify: (() => void) | null = null;\r\n\r\n const push = (chunk: Uint8Array) => {\r\n queue.push(chunk);\r\n if (notify) {\r\n notify();\r\n notify = null;\r\n }\r\n };\r\n\r\n const timeout = setTimeout(() => {\r\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\r\n this.ws.close();\r\n }\r\n }, 30000);\r\n\r\n this.ws.onopen = () => {\r\n const message = this.buildTTSConfigMessage();\r\n this.ws!.send(message);\r\n\r\n const timestamp = this.nowRFC1123();\r\n const speechMessage = `X-RequestId:${reqId}\\r\\nContent-Type:application/ssml+xml\\r\\nX-Timestamp:${timestamp}\\r\\nPath:ssml\\r\\n\\r\\n${SSML_text}`;\r\n this.ws!.send(speechMessage);\r\n };\r\n\r\n this.ws.onmessage = (event) => {\r\n if (typeof event.data === 'string') {\r\n if (event.data.includes('Path:turn.end')) {\r\n this.ws?.close();\r\n }\r\n } else {\r\n const reader = new FileReader();\r\n reader.onload = () => {\r\n const arrayBuffer = reader.result as ArrayBuffer;\r\n const uint8Array = new Uint8Array(arrayBuffer);\r\n \r\n const needle = new TextEncoder().encode(\"Path:audio\\r\\n\");\r\n const audioStartIndex = this.indexOf(uint8Array, needle);\r\n\r\n if (audioStartIndex !== -1) {\r\n const audioChunk = uint8Array.slice(audioStartIndex + needle.length);\r\n this.audio_stream.push(audioChunk);\r\n push(audioChunk);\r\n }\r\n };\r\n reader.readAsArrayBuffer(event.data);\r\n }\r\n };\r\n\r\n this.ws.onerror = (err: any) => {\r\n error = new Error('WebSocket error');\r\n done = true;\r\n if (notify) {\r\n notify();\r\n notify = null;\r\n }\r\n };\r\n\r\n this.ws.onclose = () => {\r\n clearTimeout(timeout);\r\n done = true;\r\n if (notify) {\r\n notify();\r\n notify = null;\r\n }\r\n };\r\n\r\n while (!done || queue.length > 0) {\r\n if (queue.length === 0) {\r\n await new Promise<void>(resolve => (notify = resolve));\r\n continue;\r\n }\r\n const chunk = queue.shift();\r\n if (chunk) {\r\n yield chunk;\r\n }\r\n }\r\n\r\n if (error) {\r\n throw error;\r\n }\r\n }\r\n\r\n private processAudioData(buffer: Uint8Array): void {\r\n const needle = new TextEncoder().encode(\"Path:audio\\r\\n\");\r\n const audioStartIndex = this.indexOf(buffer, needle);\r\n\r\n if (audioStartIndex !== -1) {\r\n const audioChunk = buffer.slice(audioStartIndex + needle.length);\r\n this.audio_stream.push(audioChunk);\r\n }\r\n }\r\n\r\n private indexOf(arr: Uint8Array, subarr: Uint8Array): number {\r\n for (let i = 0; i <= arr.length - subarr.length; i++) {\r\n let match = true;\r\n for (let j = 0; j < subarr.length; j++) {\r\n if (arr[i + j] !== subarr[j]) {\r\n match = false;\r\n break;\r\n }\r\n }\r\n if (match) return i;\r\n }\r\n return -1;\r\n }\r\n\r\n private async generateSecMsGec(trustedClientToken: string): Promise<string> {\r\n const now = this.nowRFC1123();\r\n const fixedDate = this.parseRFC1123(now);\r\n const ticks = Math.floor(fixedDate.getTime() / 1000) + 11644473600;\r\n const rounded = ticks - (ticks % 300);\r\n const windowsTicks = rounded * 10_000_000;\r\n\r\n const encoder = new TextEncoder();\r\n const data = encoder.encode(`${windowsTicks}${trustedClientToken}`);\r\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\r\n\r\n return Array.from(new Uint8Array(hashBuffer))\r\n .map(b => b.toString(16).padStart(2, '0'))\r\n .join('')\r\n .toUpperCase();\r\n }\r\n\r\n getAudioData(): Uint8Array {\r\n if (this.audio_stream.length === 0) {\r\n throw new Error(\"No audio data available\");\r\n }\r\n const totalLength = this.audio_stream.reduce((acc, chunk) => acc + chunk.length, 0);\r\n const result = new Uint8Array(totalLength);\r\n let offset = 0;\r\n for (const chunk of this.audio_stream) {\r\n result.set(chunk, offset);\r\n offset += chunk.length;\r\n }\r\n return result;\r\n }\r\n}\r\n"],
|
|
5
|
+
"mappings": ";;;;;AAAO,IAAM,YAAY;AAAA,EACrB,sBAAsB;AAAA,EACtB,UAAU;AAAA,EACV,SAAS;AAAA,EACT,YAAY;AAAA,EAEZ,uBAAuB;AAAA,EACvB,wBAAwB;AAAA,EACxB,gBAAgB;AAAA,EAEhB,UAAU;AACN,UAAM,QAAQ,IAAI,WAAW,EAAE;AAC/B,WAAO,gBAAgB,KAAK;AAE5B,WAAO,MAAM,KAAK,KAAK,EAClB,IAAI,OAAK,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EACxC,KAAK,EAAE,EACP,YAAY;AAAA,EACrB;AAAA,EAEA,iBAAiB;AACb,WAAO;AAAA,MACH,cAAc;AAAA,MACd,mBAAmB;AAAA,MACnB,mBAAmB;AAAA,MACnB,UAAU,UAAU,KAAK,QAAQ;AAAA,IACrC;AAAA,EACJ;AAAA,EAEA,aAAa;AAAA,IACT,UAAU;AAAA,IACV,iBAAiB;AAAA,IACjB,UAAU;AAAA,IACV,0BAA0B;AAAA,IAC1B,yBAAyB;AAAA,IACzB,cAAc;AAAA,EAClB;AAAA,EAEA,eAAe;AAAA,IACX,aAAa;AAAA,IACb,oBAAoB;AAAA,IACpB,UAAU;AAAA,IACV,kBAAkB;AAAA,IAClB,kBAAkB;AAAA,IAClB,kBAAkB;AAAA,EACtB;AACJ;;;ACtBO,IAAM,UAAN,MAAc;AAAA,EAAd;AACH,wBAAQ,gBAA6B,CAAC;AACtC,wBAAQ;AAAA;AAAA,EAER,MAAM,YAA8B;AAChC,UAAM,WAAW,MAAM,KAAK,iBAAiB,UAAU,oBAAoB;AAE3E,UAAM,WAAW,MAAM;AAAA,MACnB,GAAG,UAAU,UAAU,8BAA8B,UAAU,oBAAoB,eAAe,QAAQ,uBAAuB,UAAU,cAAc;AAAA,MACzJ,EAAE,SAAS,UAAU,eAAe,EAAE;AAAA,IAC1C;AAEA,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,WAAO,KAAK,IAAI,CAAC,UAAe;AAC5B,YAAM,eAAe,MAAM,gBAAgB,MAAM;AACjD,aAAO,MAAM;AACb,aAAO,MAAM;AACb,aAAO;AAAA,IACX,CAAC;AAAA,EACL;AAAA,EAEA,MAAM,oBAAoB,QAAkC;AACxD,UAAM,SAAS,MAAM,KAAK,UAAU;AACpC,WAAO,OAAO,OAAO,WAAS,MAAM,OAAO,WAAW,MAAM,CAAC;AAAA,EACjE;AAAA,EAEA,MAAM,kBAAkB,QAA6C;AACjE,UAAM,SAAS,MAAM,KAAK,UAAU;AACpC,WAAO,OAAO,OAAO,WAAS,MAAM,WAAW,MAAM;AAAA,EACzD;AAAA,EAEQ,eAAuB;AAC3B,WAAO,uCAAuC,QAAQ,SAAS,SAAU,GAAG;AACxE,YAAM,IAAI,KAAK,OAAO,IAAI,KAAK;AAC/B,YAAM,IAAI,MAAM,MAAM,IAAK,IAAI,IAAM;AACrC,aAAO,EAAE,SAAS,EAAE;AAAA,IACxB,CAAC;AAAA,EACL;AAAA,EAEQ,cAAc,OAAgC;AAClD,QAAI,OAAO,UAAU,UAAU;AAC3B,aAAQ,SAAS,IAAI,IAAI,KAAK,OAAO,GAAG,KAAK;AAAA,IACjD;AACA,WAAO;AAAA,EACX;AAAA,EAEQ,aAAa,MAA+B;AAChD,QAAI;AACJ,QAAI,OAAO,SAAS,UAAU;AAC1B,kBAAY,WAAW,KAAK,QAAQ,KAAK,EAAE,CAAC;AAAA,IAChD,OAAO;AACH,kBAAY;AAAA,IAChB;AACA,WAAO,aAAa,IAAI,IAAI,SAAS,MAAM,GAAG,SAAS;AAAA,EAC3D;AAAA,EAEQ,eAAe,QAAiC;AACpD,QAAI;AACJ,QAAI,OAAO,WAAW,UAAU;AAC5B,oBAAc,SAAS,OAAO,QAAQ,KAAK,EAAE,GAAG,EAAE;AAAA,IACtD,OAAO;AACH,oBAAc;AAAA,IAClB;AACA,WAAO,GAAG,WAAW;AAAA,EACzB;AAAA,EAEQ,WAAW,SAAuC;AACtD,UAAM,iBAAiB,QAAQ,KAAK;AACpC,UAAM,gBAAgB,mBAAmB,KAAK,cAAc;AAE5D,QAAI,CAAC,eAAe;AAChB,aAAO,EAAE,SAAS,MAAM,QAAQ,MAAM;AAAA,IAC1C;AAEA,UAAM,SAAmB,CAAC;AAC1B,UAAM,cAAc,kCAAkC,KAAK,cAAc;AACzE,UAAM,cAAc,kCAAkC,KAAK,cAAc;AAEzE,QAAI,CAAC,aAAa;AACd,YAAM,IAAI,MAAM,mCAAmC;AAAA,IACvD;AAEA,QAAI,CAAC,aAAa;AACd,YAAM,IAAI,MAAM,mCAAmC;AAAA,IACvD;AAEA,UAAM,sBAAsB,sDAAsD,KAAK,cAAc;AACrG,QAAI,CAAC,uBAAuB,aAAa;AACrC,YAAM,IAAI,MAAM,0DAA0D;AAAA,IAC9E;AAEA,WAAO;AAAA,MACH,SAAS,OAAO,WAAW;AAAA,MAC3B,QAAQ,eAAe;AAAA,MACvB,QAAQ,OAAO,SAAS,IAAI,SAAS;AAAA,IACzC;AAAA,EACJ;AAAA,EAEQ,UAAU,MAAsB;AACpC,WAAO,KACF,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,QAAQ,EACtB,QAAQ,MAAM,QAAQ;AAAA,EAC/B;AAAA,EAEQ,QAAQ,SAAiB,OAAe,UAA4B,CAAC,GAAW;AACpF,UAAM,YAAY,QAAQ,aAAa;AACvC,QAAI,cAAc;AAElB,QAAI,cAAc,QAAQ;AACtB,oBAAc;AAAA,IAClB,WAAW,cAAc,QAAQ;AAC7B,oBAAc;AAAA,IAClB,OAAO;AACH,YAAM,YAAY,KAAK,WAAW,OAAO;AACzC,oBAAc,UAAU;AAExB,UAAI,UAAU,QAAQ;AAClB,gBAAQ,IAAI,4BAAuB;AACnC,YAAI,CAAC,UAAU,SAAS;AACpB,kBAAQ,KAAK,oCAA+B,UAAU,MAAM;AAAA,QAChE;AAAA,MACJ,OAAO;AACH,gBAAQ,IAAI,kCAA6B;AAAA,MAC7C;AAAA,IACJ;AAEA,QAAI,aAAa;AACb,UAAI,OAAO,QAAQ,KAAK;AAExB,UAAI,CAAC,KAAK,SAAS,QAAQ,GAAG;AAC1B,eAAO,KAAK;AAAA,UACR;AAAA,UACA;AAAA,QACJ;AAAA,MACJ;AAEA,UAAI,CAAC,kBAAkB,KAAK,IAAI,KAAK,OAAO;AACxC,eAAO,KAAK;AAAA,UACR;AAAA,UACA,kBAAkB,KAAK;AAAA,QAC3B;AAAA,MACJ;AAEA,aAAO;AAAA,IACX;AAEA,UAAM,QAAQ,KAAK,cAAc,QAAQ,SAAS,CAAC;AACnD,UAAM,OAAO,KAAK,aAAa,QAAQ,QAAQ,CAAC;AAChD,UAAM,SAAS,KAAK,eAAe,QAAQ,UAAU,CAAC;AACtD,UAAM,cAAc,KAAK,UAAU,OAAO;AAE1C,WAAO;AAAA,mCACoB,KAAK;AAAA,0CACE,KAAK,WAAW,IAAI,aAAa,MAAM;AAAA,8BACnD,WAAW;AAAA;AAAA;AAAA;AAAA,EAIrC;AAAA,EAEQ,aAAqB;AACzB,YAAO,oBAAI,KAAK,GAAE,YAAY;AAAA,EAClC;AAAA,EAEQ,aAAa,QAAsB;AACvC,WAAO,IAAI,KAAK,MAAM;AAAA,EAC1B;AAAA,EAEQ,wBAAgC;AACpC,UAAM,YAAY,KAAK,WAAW;AAClC,WAAO,eAAe,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,EAEnC;AAAA,EAEA,MAAM,WAAW,MAAc,QAAgB,qBAAqB,UAA4B,CAAC,GAAkB;AAC/G,UAAM,WAAW,MAAM,KAAK,iBAAiB,UAAU,oBAAoB;AAE3E,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACpC,WAAK,eAAe,CAAC;AACrB,YAAM,QAAQ,KAAK,aAAa;AAChC,YAAM,MAAM,GAAG,UAAU,OAAO,8BAA8B,UAAU,oBAAoB,eAAe,QAAQ,uBAAuB,UAAU,cAAc,iBAAiB,KAAK;AAExL,cAAQ,IAAI,kBAAkB,GAAG;AACjC,WAAK,KAAK,IAAI,UAAU,GAAG;AAE3B,YAAM,YAAY,KAAK,QAAQ,MAAM,OAAO,OAAO;AAEnD,YAAM,UAAU,WAAW,MAAM;AAC7B,YAAI,KAAK,MAAM,KAAK,GAAG,eAAe,UAAU,MAAM;AAClD,eAAK,GAAG,MAAM;AAAA,QAClB;AACA,eAAO,IAAI,MAAM,mBAAmB,CAAC;AAAA,MACzC,GAAG,GAAK;AAER,WAAK,GAAG,SAAS,MAAM;AACnB,cAAM,UAAU,KAAK,sBAAsB;AAC3C,aAAK,GAAI,KAAK,OAAO;AACrB,cAAM,YAAY,KAAK,WAAW;AAClC,cAAM,gBAAgB,eAAe,KAAK;AAAA;AAAA,cAAwD,SAAS;AAAA;AAAA;AAAA,EAAwB,SAAS;AAC5I,aAAK,GAAI,KAAK,aAAa;AAAA,MAC/B;AAEA,WAAK,GAAG,YAAY,CAAC,UAAU;AAC3B,YAAI,OAAO,MAAM,SAAS,UAAU;AAChC,cAAI,MAAM,KAAK,SAAS,eAAe,GAAG;AACtC,iBAAK,IAAI,MAAM;AAAA,UACnB;AAAA,QACJ,OAAO;AACH,gBAAM,SAAS,IAAI,WAAW;AAC9B,iBAAO,SAAS,MAAM;AAClB,kBAAM,cAAc,OAAO;AAC3B,kBAAM,aAAa,IAAI,WAAW,WAAW;AAC7C,iBAAK,iBAAiB,UAAU;AAAA,UACpC;AACA,iBAAO,kBAAkB,MAAM,IAAI;AAAA,QACvC;AAAA,MACJ;AAEA,WAAK,GAAG,UAAU,CAAC,QAAa;AAC5B,qBAAa,OAAO;AACpB,YAAI,KAAK,MAAM,KAAK,GAAG,eAAe,UAAU,MAAM;AAClD,eAAK,GAAG,MAAM;AAAA,QAClB;AACA,eAAO,IAAI,MAAM,iBAAiB,CAAC;AAAA,MACvC;AAEA,WAAK,GAAG,UAAU,MAAM;AACpB,qBAAa,OAAO;AACpB,gBAAQ;AAAA,MACZ;AAAA,IACJ,CAAC;AAAA,EACL;AAAA,EAEA,OAAO,iBAAiB,MAAc,QAAgB,qBAAqB,UAA4B,CAAC,GAA8C;AAClJ,SAAK,eAAe,CAAC;AAErB,UAAM,QAAQ,KAAK,aAAa;AAChC,UAAM,WAAW,MAAM,KAAK,iBAAiB,UAAU,oBAAoB;AAE3E,UAAM,MAAM,GAAG,UAAU,OAAO,8BAA8B,UAAU,oBAAoB,eAAe,QAAQ,uBAAuB,UAAU,cAAc,iBAAiB,KAAK;AAExL,SAAK,KAAK,IAAI,UAAU,GAAG;AAE3B,UAAM,YAAY,KAAK,QAAQ,MAAM,OAAO,OAAO;AAEnD,UAAM,QAAsB,CAAC;AAC7B,QAAI,OAAO;AACX,QAAI,QAAsB;AAC1B,QAAI,SAA8B;AAElC,UAAM,OAAO,CAAC,UAAsB;AAChC,YAAM,KAAK,KAAK;AAChB,UAAI,QAAQ;AACR,eAAO;AACP,iBAAS;AAAA,MACb;AAAA,IACJ;AAEA,UAAM,UAAU,WAAW,MAAM;AAC7B,UAAI,KAAK,MAAM,KAAK,GAAG,eAAe,UAAU,MAAM;AAClD,aAAK,GAAG,MAAM;AAAA,MAClB;AAAA,IACJ,GAAG,GAAK;AAER,SAAK,GAAG,SAAS,MAAM;AACnB,YAAM,UAAU,KAAK,sBAAsB;AAC3C,WAAK,GAAI,KAAK,OAAO;AAErB,YAAM,YAAY,KAAK,WAAW;AAClC,YAAM,gBAAgB,eAAe,KAAK;AAAA;AAAA,cAAwD,SAAS;AAAA;AAAA;AAAA,EAAwB,SAAS;AAC5I,WAAK,GAAI,KAAK,aAAa;AAAA,IAC/B;AAEA,SAAK,GAAG,YAAY,CAAC,UAAU;AAC3B,UAAI,OAAO,MAAM,SAAS,UAAU;AAChC,YAAI,MAAM,KAAK,SAAS,eAAe,GAAG;AACtC,eAAK,IAAI,MAAM;AAAA,QACnB;AAAA,MACJ,OAAO;AACH,cAAM,SAAS,IAAI,WAAW;AAC9B,eAAO,SAAS,MAAM;AAClB,gBAAM,cAAc,OAAO;AAC3B,gBAAM,aAAa,IAAI,WAAW,WAAW;AAE7C,gBAAM,SAAS,IAAI,YAAY,EAAE,OAAO,gBAAgB;AACxD,gBAAM,kBAAkB,KAAK,QAAQ,YAAY,MAAM;AAEvD,cAAI,oBAAoB,IAAI;AACxB,kBAAM,aAAa,WAAW,MAAM,kBAAkB,OAAO,MAAM;AACnE,iBAAK,aAAa,KAAK,UAAU;AACjC,iBAAK,UAAU;AAAA,UACnB;AAAA,QACJ;AACA,eAAO,kBAAkB,MAAM,IAAI;AAAA,MACvC;AAAA,IACJ;AAEA,SAAK,GAAG,UAAU,CAAC,QAAa;AAC5B,cAAQ,IAAI,MAAM,iBAAiB;AACnC,aAAO;AACP,UAAI,QAAQ;AACR,eAAO;AACP,iBAAS;AAAA,MACb;AAAA,IACJ;AAEA,SAAK,GAAG,UAAU,MAAM;AACpB,mBAAa,OAAO;AACpB,aAAO;AACP,UAAI,QAAQ;AACR,eAAO;AACP,iBAAS;AAAA,MACb;AAAA,IACJ;AAEA,WAAO,CAAC,QAAQ,MAAM,SAAS,GAAG;AAC9B,UAAI,MAAM,WAAW,GAAG;AACpB,cAAM,IAAI,QAAc,aAAY,SAAS,OAAQ;AACrD;AAAA,MACJ;AACA,YAAM,QAAQ,MAAM,MAAM;AAC1B,UAAI,OAAO;AACP,cAAM;AAAA,MACV;AAAA,IACJ;AAEA,QAAI,OAAO;AACP,YAAM;AAAA,IACV;AAAA,EACJ;AAAA,EAEQ,iBAAiB,QAA0B;AAC/C,UAAM,SAAS,IAAI,YAAY,EAAE,OAAO,gBAAgB;AACxD,UAAM,kBAAkB,KAAK,QAAQ,QAAQ,MAAM;AAEnD,QAAI,oBAAoB,IAAI;AACxB,YAAM,aAAa,OAAO,MAAM,kBAAkB,OAAO,MAAM;AAC/D,WAAK,aAAa,KAAK,UAAU;AAAA,IACrC;AAAA,EACJ;AAAA,EAEQ,QAAQ,KAAiB,QAA4B;AACzD,aAAS,IAAI,GAAG,KAAK,IAAI,SAAS,OAAO,QAAQ,KAAK;AAClD,UAAI,QAAQ;AACZ,eAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACpC,YAAI,IAAI,IAAI,CAAC,MAAM,OAAO,CAAC,GAAG;AAC1B,kBAAQ;AACR;AAAA,QACJ;AAAA,MACJ;AACA,UAAI,MAAO,QAAO;AAAA,IACtB;AACA,WAAO;AAAA,EACX;AAAA,EAEA,MAAc,iBAAiB,oBAA6C;AACxE,UAAM,MAAM,KAAK,WAAW;AAC5B,UAAM,YAAY,KAAK,aAAa,GAAG;AACvC,UAAM,QAAQ,KAAK,MAAM,UAAU,QAAQ,IAAI,GAAI,IAAI;AACvD,UAAM,UAAU,QAAS,QAAQ;AACjC,UAAM,eAAe,UAAU;AAE/B,UAAM,UAAU,IAAI,YAAY;AAChC,UAAM,OAAO,QAAQ,OAAO,GAAG,YAAY,GAAG,kBAAkB,EAAE;AAClE,UAAM,aAAa,MAAM,OAAO,OAAO,OAAO,WAAW,IAAI;AAE7D,WAAO,MAAM,KAAK,IAAI,WAAW,UAAU,CAAC,EACvC,IAAI,OAAK,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EACxC,KAAK,EAAE,EACP,YAAY;AAAA,EACrB;AAAA,EAEA,eAA2B;AACvB,QAAI,KAAK,aAAa,WAAW,GAAG;AAChC,YAAM,IAAI,MAAM,yBAAyB;AAAA,IAC7C;AACA,UAAM,cAAc,KAAK,aAAa,OAAO,CAAC,KAAK,UAAU,MAAM,MAAM,QAAQ,CAAC;AAClF,UAAM,SAAS,IAAI,WAAW,WAAW;AACzC,QAAI,SAAS;AACb,eAAW,SAAS,KAAK,cAAc;AACnC,aAAO,IAAI,OAAO,MAAM;AACxB,gBAAU,MAAM;AAAA,IACpB;AACA,WAAO;AAAA,EACX;AACJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
var T=Object.defineProperty;var v=(u,e,t)=>e in u?T(u,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):u[e]=t;var f=(u,e,t)=>v(u,typeof e!="symbol"?e+"":e,t);var a={TRUSTED_CLIENT_TOKEN:"6A5AA1D4EAFF4E9FB37E23D68491D6F4",BASE_URL:"https://api.msedgeservices.com/tts/cognitiveservices",WSS_URL:"wss://api.msedgeservices.com/tts/cognitiveservices/websocket/v1",VOICES_URL:"https://api.msedgeservices.com/tts/cognitiveservices/voices/list",CHROMIUM_FULL_VERSION:"142.0.3595.0",CHROMIUM_MAJOR_VERSION:"142",VERSION_MS_GEC:"1-142.0.3595",token32(){let u=new Uint8Array(16);return crypto.getRandomValues(u),Array.from(u).map(e=>e.toString(16).padStart(2,"0")).join("").toUpperCase()},getBaseHeaders(){return{"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0","Accept-Encoding":"gzip, deflate, br, zstd","Accept-Language":"es,es-ES;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,es-CO;q=0.5,es-MX;q=0.4",Cookie:"MUID="+this.token32()}},WSS_HEADERS:{Pragma:"no-cache","Cache-Control":"no-cache",Origin:"chrome-extension://jdiccldimpdaibmpdkjnbmckianbfold","Sec-WebSocket-Protocol":"synthesize","Sec-WebSocket-Version":"13","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0"},VOICE_HEADERS:{"Sec-CH-UA":'" Not;A Brand";v="99", "Microsoft Edge";v="140", "Chromium";v="140"',"Sec-CH-UA-Mobile":"?0",Accept:"*/*","Sec-Fetch-Site":"none","Sec-Fetch-Mode":"cors","Sec-Fetch-Dest":"empty"}};var C=class{constructor(){f(this,"audio_stream",[]);f(this,"ws")}async getVoices(){let e=await this.generateSecMsGec(a.TRUSTED_CLIENT_TOKEN);return(await(await fetch(`${a.VOICES_URL}?Ocp-Apim-Subscription-Key=${a.TRUSTED_CLIENT_TOKEN}&Sec-MS-GEC=${e}&Sec-MS-GEC-Version=${a.VERSION_MS_GEC}`,{headers:a.getBaseHeaders()})).json()).map(s=>(s.FriendlyName=s.FriendlyName||s.LocalName,delete s.SampleRateHertz,delete s.Status,s))}async getVoicesByLanguage(e){return(await this.getVoices()).filter(n=>n.Locale.startsWith(e))}async getVoicesByGender(e){return(await this.getVoices()).filter(n=>n.Gender===e)}generateUUID(){return"xxxxxxxx-xxxx-xxxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){let t=Math.random()*16|0;return(e==="x"?t:t&3|8).toString(16)})}validatePitch(e){return typeof e=="number"?e>=0?`+${e}Hz`:`${e}Hz`:e}validateRate(e){let t;return typeof e=="string"?t=parseFloat(e.replace("%","")):t=e,t>=0?`+${t}%`:`${t}%`}validateVolume(e){let t;return typeof e=="string"?t=parseInt(e.replace("%",""),10):t=e,`${t}%`}detectSSML(e){let t=e.trim();if(!/^<\?xml|^<speak/i.test(t))return{isValid:!0,isSSML:!1};let s=[],i=/<speak\b[^>]*>[\s\S]*<\/speak>/i.test(t),l=/<voice\b[^>]*>[\s\S]*<\/voice>/i.test(t);if(!i)throw new Error("Invalid SSML: Missing <speak> tag");if(!l)throw new Error("Invalid SSML: Missing <voice> tag");if(!/xmlns="http:\/\/www\.w3\.org\/2001\/10\/synthesis"/i.test(t)&&i)throw new Error("Invalid SSML: Missing or incorrect namespace declaration");return{isValid:s.length===0,isSSML:i||l,errors:s.length>0?s:void 0}}escapeXML(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}getSSML(e,t,n={}){let s=n.inputType||"auto",i=!1;if(s==="ssml")i=!0;else if(s==="text")i=!1;else{let r=this.detectSSML(e);i=r.isSSML,r.isSSML?(console.log("\u2192 Detected SSML input"),r.isValid||console.warn("\u26A0 SSML validation warnings:",r.errors)):console.log("\u2192 Detected plain text input")}if(i){let r=e.trim();return r.includes("xmlns=")||(r=r.replace(/<speak([^>]*)>/i,'<speak$1 xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="https://www.w3.org/2001/mstts">')),!/<voice\b[^>]*>/i.test(r)&&t&&(r=r.replace(/(<speak[^>]*>)([\s\S]*?)(<\/speak>)/i,`$1<voice name="${t}">$2</voice>$3`)),r}let l=this.validatePitch(n.pitch??0),p=this.validateRate(n.rate??0),d=this.validateVolume(n.volume??0),S=this.escapeXML(e);return`<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="https://www.w3.org/2001/mstts" xml:lang="en-US">
|
|
2
|
+
<voice name="${t}">
|
|
3
|
+
<prosody pitch="${l}" rate="${p}" volume="${d}">
|
|
4
|
+
${S}
|
|
5
|
+
</prosody>
|
|
6
|
+
</voice>
|
|
7
|
+
</speak>`}nowRFC1123(){return new Date().toUTCString()}parseRFC1123(e){return new Date(e)}buildTTSConfigMessage(){return`X-Timestamp:${this.nowRFC1123()}\r
|
|
8
|
+
Content-Type:application/json; charset=utf-8\r
|
|
9
|
+
Path:speech.config\r
|
|
10
|
+
\r
|
|
11
|
+
{"context":{"synthesis":{"audio":{"metadataoptions":{"sentenceBoundaryEnabled":false,"wordBoundaryEnabled":true},"outputFormat":"audio-24khz-48kbitrate-mono-mp3"}}}}`}async synthesize(e,t="en-US-JennyNeural",n={}){let s=await this.generateSecMsGec(a.TRUSTED_CLIENT_TOKEN);return new Promise((i,l)=>{this.audio_stream=[];let p=this.generateUUID(),d=`${a.WSS_URL}?Ocp-Apim-Subscription-Key=${a.TRUSTED_CLIENT_TOKEN}&Sec-MS-GEC=${s}&Sec-MS-GEC-Version=${a.VERSION_MS_GEC}&ConnectionId=${p}`;console.log("WebSocket URL:",d),this.ws=new WebSocket(d);let S=this.getSSML(e,t,n),r=setTimeout(()=>{this.ws&&this.ws.readyState===WebSocket.OPEN&&this.ws.close(),l(new Error("Synthesis timeout"))},3e4);this.ws.onopen=()=>{let o=this.buildTTSConfigMessage();this.ws.send(o);let h=this.nowRFC1123(),m=`X-RequestId:${p}\r
|
|
12
|
+
Content-Type:application/ssml+xml\r
|
|
13
|
+
X-Timestamp:${h}\r
|
|
14
|
+
Path:ssml\r
|
|
15
|
+
\r
|
|
16
|
+
${S}`;this.ws.send(m)},this.ws.onmessage=o=>{if(typeof o.data=="string")o.data.includes("Path:turn.end")&&this.ws?.close();else{let h=new FileReader;h.onload=()=>{let m=h.result,c=new Uint8Array(m);this.processAudioData(c)},h.readAsArrayBuffer(o.data)}},this.ws.onerror=o=>{clearTimeout(r),this.ws&&this.ws.readyState===WebSocket.OPEN&&this.ws.close(),l(new Error("WebSocket error"))},this.ws.onclose=()=>{clearTimeout(r),i()}})}async*synthesizeStream(e,t="en-US-JennyNeural",n={}){this.audio_stream=[];let s=this.generateUUID(),i=await this.generateSecMsGec(a.TRUSTED_CLIENT_TOKEN),l=`${a.WSS_URL}?Ocp-Apim-Subscription-Key=${a.TRUSTED_CLIENT_TOKEN}&Sec-MS-GEC=${i}&Sec-MS-GEC-Version=${a.VERSION_MS_GEC}&ConnectionId=${s}`;this.ws=new WebSocket(l);let p=this.getSSML(e,t,n),d=[],S=!1,r=null,o=null,h=c=>{d.push(c),o&&(o(),o=null)},m=setTimeout(()=>{this.ws&&this.ws.readyState===WebSocket.OPEN&&this.ws.close()},3e4);for(this.ws.onopen=()=>{let c=this.buildTTSConfigMessage();this.ws.send(c);let g=this.nowRFC1123(),w=`X-RequestId:${s}\r
|
|
17
|
+
Content-Type:application/ssml+xml\r
|
|
18
|
+
X-Timestamp:${g}\r
|
|
19
|
+
Path:ssml\r
|
|
20
|
+
\r
|
|
21
|
+
${p}`;this.ws.send(w)},this.ws.onmessage=c=>{if(typeof c.data=="string")c.data.includes("Path:turn.end")&&this.ws?.close();else{let g=new FileReader;g.onload=()=>{let w=g.result,y=new Uint8Array(w),E=new TextEncoder().encode(`Path:audio\r
|
|
22
|
+
`),x=this.indexOf(y,E);if(x!==-1){let M=y.slice(x+E.length);this.audio_stream.push(M),h(M)}},g.readAsArrayBuffer(c.data)}},this.ws.onerror=c=>{r=new Error("WebSocket error"),S=!0,o&&(o(),o=null)},this.ws.onclose=()=>{clearTimeout(m),S=!0,o&&(o(),o=null)};!S||d.length>0;){if(d.length===0){await new Promise(g=>o=g);continue}let c=d.shift();c&&(yield c)}if(r)throw r}processAudioData(e){let t=new TextEncoder().encode(`Path:audio\r
|
|
23
|
+
`),n=this.indexOf(e,t);if(n!==-1){let s=e.slice(n+t.length);this.audio_stream.push(s)}}indexOf(e,t){for(let n=0;n<=e.length-t.length;n++){let s=!0;for(let i=0;i<t.length;i++)if(e[n+i]!==t[i]){s=!1;break}if(s)return n}return-1}async generateSecMsGec(e){let t=this.nowRFC1123(),n=this.parseRFC1123(t),s=Math.floor(n.getTime()/1e3)+11644473600,l=(s-s%300)*1e7,d=new TextEncoder().encode(`${l}${e}`),S=await crypto.subtle.digest("SHA-256",d);return Array.from(new Uint8Array(S)).map(r=>r.toString(16).padStart(2,"0")).join("").toUpperCase()}getAudioData(){if(this.audio_stream.length===0)throw new Error("No audio data available");let e=this.audio_stream.reduce((s,i)=>s+i.length,0),t=new Uint8Array(e),n=0;for(let s of this.audio_stream)t.set(s,n),n+=s.length;return t}};export{C as EdgeTTS};
|
|
24
|
+
//# sourceMappingURL=edge-tts.esm.min.js.map
|