@andresaya/edge-tts 1.3.0 → 1.3.2

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 CHANGED
@@ -1,4 +1,5 @@
1
1
  # Edge TTS
2
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/andresayac/edge-tts)
2
3
 
3
4
  **Edge TTS** is a powerful Text-to-Speech (TTS) package that leverages Microsoft's Edge capabilities. This package allows you to synthesize speech from text and manage voice options easily through a command-line interface (CLI).
4
5
 
@@ -6,7 +6,7 @@ const VoiceListCommand_1 = require("../commands/VoiceListCommand");
6
6
  const commander_1 = require("commander");
7
7
  const program = new commander_1.Command();
8
8
  program
9
- .version('1.2.7')
9
+ .version('1.3.2')
10
10
  .addCommand(SynthesizeCommand_1.SynthesizeCommand)
11
11
  .addCommand(VoiceListCommand_1.VoiceListCommand);
12
12
  try {
@@ -2,4 +2,5 @@ export declare const Constants: {
2
2
  TRUSTED_CLIENT_TOKEN: string;
3
3
  WSS_URL: string;
4
4
  VOICES_URL: string;
5
+ USER_AGENT: string;
5
6
  };
@@ -5,4 +5,5 @@ exports.Constants = {
5
5
  TRUSTED_CLIENT_TOKEN: '6A5AA1D4EAFF4E9FB37E23D68491D6F4',
6
6
  WSS_URL: 'wss://speech.platform.bing.com/consumer/speech/synthesize/readaloud/edge/v1',
7
7
  VOICES_URL: 'https://speech.platform.bing.com/consumer/speech/synthesize/readaloud/voices/list',
8
+ USER_AGENT: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36 Edg/140.0.0.0'
8
9
  };
@@ -0,0 +1,81 @@
1
+ export declare const AUDIO_FORMATS_STREAM: {
2
+ readonly 'raw-8khz-8bit-mono-alaw': "audio/basic";
3
+ readonly 'raw-8khz-8bit-mono-mulaw': "audio/basic";
4
+ readonly 'raw-8khz-16bit-mono-pcm': "audio/basic";
5
+ readonly 'raw-16khz-16bit-mono-pcm': "audio/basic";
6
+ readonly 'raw-22050hz-16bit-mono-pcm': "audio/basic";
7
+ readonly 'raw-24khz-16bit-mono-pcm': "audio/basic";
8
+ readonly 'raw-44100hz-16bit-mono-pcm': "audio/basic";
9
+ readonly 'raw-48khz-16bit-mono-pcm': "audio/basic";
10
+ readonly 'raw-16khz-16bit-mono-truesilk': "audio/SILK";
11
+ readonly 'raw-24khz-16bit-mono-truesilk': "audio/SILK";
12
+ readonly 'audio-16khz-32kbitrate-mono-mp3': "audio/mpeg";
13
+ readonly 'audio-16khz-64kbitrate-mono-mp3': "audio/mpeg";
14
+ readonly 'audio-16khz-128kbitrate-mono-mp3': "audio/mpeg";
15
+ readonly 'audio-24khz-48kbitrate-mono-mp3': "audio/mpeg";
16
+ readonly 'audio-24khz-96kbitrate-mono-mp3': "audio/mpeg";
17
+ readonly 'audio-24khz-160kbitrate-mono-mp3': "audio/mpeg";
18
+ readonly 'audio-48khz-96kbitrate-mono-mp3': "audio/mpeg";
19
+ readonly 'audio-48khz-192kbitrate-mono-mp3': "audio/mpeg";
20
+ readonly 'audio-16khz-16bit-32kbps-mono-opus': "audio/opus";
21
+ readonly 'audio-24khz-16bit-24kbps-mono-opus': "audio/opus";
22
+ readonly 'audio-24khz-16bit-48kbps-mono-opus': "audio/opus";
23
+ readonly 'ogg-16khz-16bit-mono-opus': "audio/ogg; codecs=opus; rate=16000";
24
+ readonly 'ogg-24khz-16bit-mono-opus': "audio/ogg; codecs=opus; rate=24000";
25
+ readonly 'ogg-48khz-16bit-mono-opus': "audio/ogg; codecs=opus; rate=48000";
26
+ readonly 'webm-16khz-16bit-mono-opus': "audio/webm; codec=opus";
27
+ readonly 'webm-24khz-16bit-mono-opus': "audio/webm; codec=opus";
28
+ readonly 'webm-24khz-16bit-24kbps-mono-opus': "audio/webm; codec=opus";
29
+ readonly 'amr-wb-16000hz': "audio/amr-wb";
30
+ readonly 'g722-16khz-64kbps': "audio/g722";
31
+ };
32
+ export declare const AUDIO_FORMATS_NON_STREAM: {
33
+ readonly 'riff-8khz-8bit-mono-alaw': "audio/x-wav";
34
+ readonly 'riff-8khz-8bit-mono-mulaw': "audio/x-wav";
35
+ readonly 'riff-8khz-16bit-mono-pcm': "audio/x-wav";
36
+ readonly 'riff-22050hz-16bit-mono-pcm': "audio/x-wav";
37
+ readonly 'riff-24khz-16bit-mono-pcm': "audio/x-wav";
38
+ readonly 'riff-44100hz-16bit-mono-pcm': "audio/x-wav";
39
+ readonly 'riff-48khz-16bit-mono-pcm': "audio/x-wav";
40
+ };
41
+ type AudioOutputFormat = keyof typeof AUDIO_FORMATS_STREAM;
42
+ declare const ALL_AUDIO_FORMATS: {
43
+ 'riff-8khz-8bit-mono-alaw': "audio/x-wav";
44
+ 'riff-8khz-8bit-mono-mulaw': "audio/x-wav";
45
+ 'riff-8khz-16bit-mono-pcm': "audio/x-wav";
46
+ 'riff-22050hz-16bit-mono-pcm': "audio/x-wav";
47
+ 'riff-24khz-16bit-mono-pcm': "audio/x-wav";
48
+ 'riff-44100hz-16bit-mono-pcm': "audio/x-wav";
49
+ 'riff-48khz-16bit-mono-pcm': "audio/x-wav";
50
+ 'raw-8khz-8bit-mono-alaw': "audio/basic";
51
+ 'raw-8khz-8bit-mono-mulaw': "audio/basic";
52
+ 'raw-8khz-16bit-mono-pcm': "audio/basic";
53
+ 'raw-16khz-16bit-mono-pcm': "audio/basic";
54
+ 'raw-22050hz-16bit-mono-pcm': "audio/basic";
55
+ 'raw-24khz-16bit-mono-pcm': "audio/basic";
56
+ 'raw-44100hz-16bit-mono-pcm': "audio/basic";
57
+ 'raw-48khz-16bit-mono-pcm': "audio/basic";
58
+ 'raw-16khz-16bit-mono-truesilk': "audio/SILK";
59
+ 'raw-24khz-16bit-mono-truesilk': "audio/SILK";
60
+ 'audio-16khz-32kbitrate-mono-mp3': "audio/mpeg";
61
+ 'audio-16khz-64kbitrate-mono-mp3': "audio/mpeg";
62
+ 'audio-16khz-128kbitrate-mono-mp3': "audio/mpeg";
63
+ 'audio-24khz-48kbitrate-mono-mp3': "audio/mpeg";
64
+ 'audio-24khz-96kbitrate-mono-mp3': "audio/mpeg";
65
+ 'audio-24khz-160kbitrate-mono-mp3': "audio/mpeg";
66
+ 'audio-48khz-96kbitrate-mono-mp3': "audio/mpeg";
67
+ 'audio-48khz-192kbitrate-mono-mp3': "audio/mpeg";
68
+ 'audio-16khz-16bit-32kbps-mono-opus': "audio/opus";
69
+ 'audio-24khz-16bit-24kbps-mono-opus': "audio/opus";
70
+ 'audio-24khz-16bit-48kbps-mono-opus': "audio/opus";
71
+ 'ogg-16khz-16bit-mono-opus': "audio/ogg; codecs=opus; rate=16000";
72
+ 'ogg-24khz-16bit-mono-opus': "audio/ogg; codecs=opus; rate=24000";
73
+ 'ogg-48khz-16bit-mono-opus': "audio/ogg; codecs=opus; rate=48000";
74
+ 'webm-16khz-16bit-mono-opus': "audio/webm; codec=opus";
75
+ 'webm-24khz-16bit-mono-opus': "audio/webm; codec=opus";
76
+ 'webm-24khz-16bit-24kbps-mono-opus': "audio/webm; codec=opus";
77
+ 'amr-wb-16000hz': "audio/amr-wb";
78
+ 'g722-16khz-64kbps': "audio/g722";
79
+ };
80
+ declare const FORMAT_CONTENT_TYPE: Map<string, "audio/basic" | "audio/SILK" | "audio/mpeg" | "audio/opus" | "audio/ogg; codecs=opus; rate=16000" | "audio/ogg; codecs=opus; rate=24000" | "audio/ogg; codecs=opus; rate=48000" | "audio/webm; codec=opus" | "audio/amr-wb" | "audio/g722" | "audio/x-wav">;
81
+ export { ALL_AUDIO_FORMATS, FORMAT_CONTENT_TYPE, AudioOutputFormat };
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FORMAT_CONTENT_TYPE = exports.ALL_AUDIO_FORMATS = exports.AUDIO_FORMATS_NON_STREAM = exports.AUDIO_FORMATS_STREAM = void 0;
4
+ exports.AUDIO_FORMATS_STREAM = {
5
+ // RAW PCM
6
+ 'raw-8khz-8bit-mono-alaw': 'audio/basic',
7
+ 'raw-8khz-8bit-mono-mulaw': 'audio/basic',
8
+ 'raw-8khz-16bit-mono-pcm': 'audio/basic',
9
+ 'raw-16khz-16bit-mono-pcm': 'audio/basic',
10
+ 'raw-22050hz-16bit-mono-pcm': 'audio/basic',
11
+ 'raw-24khz-16bit-mono-pcm': 'audio/basic',
12
+ 'raw-44100hz-16bit-mono-pcm': 'audio/basic',
13
+ 'raw-48khz-16bit-mono-pcm': 'audio/basic',
14
+ // TRUESILK
15
+ 'raw-16khz-16bit-mono-truesilk': 'audio/SILK',
16
+ 'raw-24khz-16bit-mono-truesilk': 'audio/SILK',
17
+ // MP3
18
+ 'audio-16khz-32kbitrate-mono-mp3': 'audio/mpeg',
19
+ 'audio-16khz-64kbitrate-mono-mp3': 'audio/mpeg',
20
+ 'audio-16khz-128kbitrate-mono-mp3': 'audio/mpeg',
21
+ 'audio-24khz-48kbitrate-mono-mp3': 'audio/mpeg',
22
+ 'audio-24khz-96kbitrate-mono-mp3': 'audio/mpeg',
23
+ 'audio-24khz-160kbitrate-mono-mp3': 'audio/mpeg',
24
+ 'audio-48khz-96kbitrate-mono-mp3': 'audio/mpeg',
25
+ 'audio-48khz-192kbitrate-mono-mp3': 'audio/mpeg',
26
+ // OPUS in container
27
+ 'audio-16khz-16bit-32kbps-mono-opus': 'audio/opus',
28
+ 'audio-24khz-16bit-24kbps-mono-opus': 'audio/opus',
29
+ 'audio-24khz-16bit-48kbps-mono-opus': 'audio/opus',
30
+ // OGG
31
+ 'ogg-16khz-16bit-mono-opus': 'audio/ogg; codecs=opus; rate=16000',
32
+ 'ogg-24khz-16bit-mono-opus': 'audio/ogg; codecs=opus; rate=24000',
33
+ 'ogg-48khz-16bit-mono-opus': 'audio/ogg; codecs=opus; rate=48000',
34
+ // WEBM
35
+ 'webm-16khz-16bit-mono-opus': 'audio/webm; codec=opus',
36
+ 'webm-24khz-16bit-mono-opus': 'audio/webm; codec=opus',
37
+ 'webm-24khz-16bit-24kbps-mono-opus': 'audio/webm; codec=opus',
38
+ 'amr-wb-16000hz': 'audio/amr-wb',
39
+ 'g722-16khz-64kbps': 'audio/g722'
40
+ };
41
+ exports.AUDIO_FORMATS_NON_STREAM = {
42
+ // RIFF (WAV)
43
+ 'riff-8khz-8bit-mono-alaw': 'audio/x-wav',
44
+ 'riff-8khz-8bit-mono-mulaw': 'audio/x-wav',
45
+ 'riff-8khz-16bit-mono-pcm': 'audio/x-wav',
46
+ 'riff-22050hz-16bit-mono-pcm': 'audio/x-wav',
47
+ 'riff-24khz-16bit-mono-pcm': 'audio/x-wav',
48
+ 'riff-44100hz-16bit-mono-pcm': 'audio/x-wav',
49
+ 'riff-48khz-16bit-mono-pcm': 'audio/x-wav',
50
+ };
51
+ const ALL_AUDIO_FORMATS = { ...exports.AUDIO_FORMATS_STREAM, ...exports.AUDIO_FORMATS_NON_STREAM };
52
+ exports.ALL_AUDIO_FORMATS = ALL_AUDIO_FORMATS;
53
+ const FORMAT_CONTENT_TYPE = new Map(Object.entries(ALL_AUDIO_FORMATS));
54
+ exports.FORMAT_CONTENT_TYPE = FORMAT_CONTENT_TYPE;
@@ -16,6 +16,8 @@ export declare class EdgeTTS {
16
16
  private audio_format;
17
17
  private ws;
18
18
  getVoices(): Promise<Voice[]>;
19
+ getVoicesByLanguage(locale: string): Promise<Voice[]>;
20
+ getVoicesByGender(gender: 'Male' | 'Female'): Promise<Voice[]>;
19
21
  private generateUUID;
20
22
  private validatePitch;
21
23
  private validateRate;
@@ -23,8 +25,16 @@ export declare class EdgeTTS {
23
25
  synthesize(text: string, voice?: string, options?: SynthesisOptions): Promise<void>;
24
26
  private getSSML;
25
27
  private buildTTSConfigMessage;
28
+ synthesizeStream(text: string, voice?: string, options?: SynthesisOptions): AsyncGenerator<Uint8Array, void, unknown>;
26
29
  private processAudioData;
27
- toFile(outputPath: string): Promise<string>;
30
+ private generateSecMsGec;
31
+ getDuration(): number;
32
+ getAudioInfo(): {
33
+ size: number;
34
+ format: string;
35
+ estimatedDuration: number;
36
+ };
37
+ toFile(outputPath: string, format?: string): Promise<string>;
28
38
  toRaw(): string;
29
39
  toBase64(): string;
30
40
  toBuffer(): Buffer;
@@ -28,7 +28,11 @@ class EdgeTTS {
28
28
  audio_format = 'mp3';
29
29
  ws;
30
30
  async getVoices() {
31
- const response = await fetch(`${constants_1.Constants.VOICES_URL}?trustedclienttoken=${constants_1.Constants.TRUSTED_CLIENT_TOKEN}`);
31
+ const response = await fetch(`${constants_1.Constants.VOICES_URL}?trustedclienttoken=${constants_1.Constants.TRUSTED_CLIENT_TOKEN}`, {
32
+ headers: {
33
+ "User-Agent": constants_1.Constants.USER_AGENT
34
+ },
35
+ });
32
36
  const data = await response.json();
33
37
  return data.map((voice) => {
34
38
  delete voice.VoiceTag;
@@ -37,6 +41,14 @@ class EdgeTTS {
37
41
  return voice;
38
42
  });
39
43
  }
44
+ async getVoicesByLanguage(locale) {
45
+ const voices = await this.getVoices();
46
+ return voices.filter(voice => voice.Locale.startsWith(locale));
47
+ }
48
+ async getVoicesByGender(gender) {
49
+ const voices = await this.getVoices();
50
+ return voices.filter(voice => voice.Gender === gender);
51
+ }
40
52
  generateUUID() {
41
53
  return 'xxxxxxxx-xxxx-xxxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
42
54
  const r = Math.random() * 16 | 0;
@@ -84,10 +96,16 @@ class EdgeTTS {
84
96
  return `${volumeValue}%`;
85
97
  }
86
98
  async synthesize(text, voice = 'en-US-AnaNeural', options = {}) {
99
+ const secMsGEC = await this.generateSecMsGec(constants_1.Constants.TRUSTED_CLIENT_TOKEN);
87
100
  return new Promise((resolve, reject) => {
88
101
  this.audio_stream = [];
89
102
  const req_id = this.generateUUID();
90
- this.ws = new ws_1.default(`${constants_1.Constants.WSS_URL}?trustedclienttoken=${constants_1.Constants.TRUSTED_CLIENT_TOKEN}&ConnectionId=${req_id}`);
103
+ const url = `${constants_1.Constants.WSS_URL}?TrustedClientToken=${constants_1.Constants.TRUSTED_CLIENT_TOKEN}&Sec-MS-GEC=${secMsGEC}&Sec-MS-GEC-Version=1-130.0.2849.68&ConnectionId=${req_id}`;
104
+ this.ws = new ws_1.default(url, {
105
+ headers: {
106
+ "User-Agent": constants_1.Constants.USER_AGENT
107
+ }
108
+ });
91
109
  const SSML_text = this.getSSML(text, voice, options);
92
110
  const timeout = setTimeout(() => {
93
111
  if (this.ws && this.ws.readyState === ws_1.default.OPEN) {
@@ -130,6 +148,79 @@ class EdgeTTS {
130
148
  return `X-Timestamp:${new Date().toISOString()}Z\r\nContent-Type:application/json; charset=utf-8\r\nPath:speech.config\r\n\r\n` +
131
149
  `{"context":{"synthesis":{"audio":{"metadataoptions":{"sentenceBoundaryEnabled":false,"wordBoundaryEnabled":true},"outputFormat":"audio-24khz-48kbitrate-mono-mp3"}}}}`;
132
150
  }
151
+ async *synthesizeStream(text, voice = 'en-US-AnaNeural', options = {}) {
152
+ this.audio_stream = [];
153
+ const req_id = this.generateUUID();
154
+ const secMsGEC = await this.generateSecMsGec(constants_1.Constants.TRUSTED_CLIENT_TOKEN);
155
+ const url = `${constants_1.Constants.WSS_URL}?TrustedClientToken=${constants_1.Constants.TRUSTED_CLIENT_TOKEN}&Sec-MS-GEC=${secMsGEC}&Sec-MS-GEC-Version=1-130.0.2849.68&ConnectionId=${req_id}`;
156
+ this.ws = new ws_1.default(url);
157
+ const SSML_text = this.getSSML(text, voice, options);
158
+ const queue = [];
159
+ let done = false;
160
+ let error = null;
161
+ let notify = null;
162
+ const push = (chunk) => {
163
+ queue.push(chunk);
164
+ if (notify) {
165
+ notify();
166
+ notify = null;
167
+ }
168
+ };
169
+ const timeout = setTimeout(() => {
170
+ if (this.ws && this.ws.readyState === ws_1.default.OPEN) {
171
+ this.ws.close();
172
+ }
173
+ }, 30000);
174
+ this.ws.on('open', () => {
175
+ const message = this.buildTTSConfigMessage();
176
+ this.ws.send(message);
177
+ const speechMessage = `X-RequestId:${req_id}\r\nContent-Type:application/ssml+xml\r\nX-Timestamp:${new Date().toISOString()}Z\r\nPath:ssml\r\n\r\n${SSML_text}`;
178
+ this.ws.send(speechMessage);
179
+ });
180
+ this.ws.on('message', (data) => {
181
+ const buffer = ensureBuffer(data);
182
+ const needle = buffer_1.Buffer.from('Path:audio\r\n');
183
+ const audioStartIndex = buffer.indexOf(new Uint8Array(needle));
184
+ if (audioStartIndex !== -1) {
185
+ const audioChunk = buffer.subarray(audioStartIndex + needle.length);
186
+ const chunk = new Uint8Array(audioChunk);
187
+ this.audio_stream.push(chunk);
188
+ push(chunk);
189
+ }
190
+ if (buffer.toString().includes('Path:turn.end')) {
191
+ this.ws?.close();
192
+ }
193
+ });
194
+ this.ws.on('error', (err) => {
195
+ error = err;
196
+ done = true;
197
+ if (notify) {
198
+ notify();
199
+ notify = null;
200
+ }
201
+ });
202
+ this.ws.on('close', () => {
203
+ clearTimeout(timeout);
204
+ done = true;
205
+ if (notify) {
206
+ notify();
207
+ notify = null;
208
+ }
209
+ });
210
+ while (!done || queue.length > 0) {
211
+ if (queue.length === 0) {
212
+ await new Promise(resolve => (notify = resolve));
213
+ continue;
214
+ }
215
+ const chunk = queue.shift();
216
+ if (chunk) {
217
+ yield chunk;
218
+ }
219
+ }
220
+ if (error) {
221
+ throw error;
222
+ }
223
+ }
133
224
  processAudioData(data) {
134
225
  const buffer = ensureBuffer(data);
135
226
  const needle = buffer_1.Buffer.from("Path:audio\r\n");
@@ -142,9 +233,40 @@ class EdgeTTS {
142
233
  this.ws?.close();
143
234
  }
144
235
  }
145
- async toFile(outputPath) {
236
+ async generateSecMsGec(trustedClientToken) {
237
+ const ticks = Math.floor(Date.now() / 1000) + 11644473600;
238
+ const rounded = ticks - (ticks % 300);
239
+ const windowsTicks = rounded * 10000000;
240
+ const encoder = new TextEncoder();
241
+ const data = encoder.encode(`${windowsTicks}${trustedClientToken}`);
242
+ const hashBuffer = await crypto.subtle.digest('SHA-256', data);
243
+ return Array.from(new Uint8Array(hashBuffer))
244
+ .map(b => b.toString(16).padStart(2, '0'))
245
+ .join('')
246
+ .toUpperCase();
247
+ }
248
+ getDuration() {
249
+ if (this.audio_stream.length === 0) {
250
+ throw new Error("No audio data available");
251
+ }
252
+ // Estimate duration based on the size of the audio stream
253
+ const bufferSize = this.toBuffer().length;
254
+ const estimatedDuration = bufferSize / (24000 * 3); // 24000 Hz sample rate, 3 bytes per sample (16-bit stereo)
255
+ return estimatedDuration;
256
+ }
257
+ getAudioInfo() {
258
+ const buffer = this.toBuffer();
259
+ return {
260
+ size: buffer.length,
261
+ format: this.audio_format,
262
+ estimatedDuration: this.getDuration()
263
+ };
264
+ }
265
+ async toFile(outputPath, format = this.audio_format) {
266
+ if (!format || typeof format !== 'string')
267
+ format = this.audio_format;
146
268
  const audioBuffer = this.toBuffer();
147
- const finalPath = `${outputPath}.${this.audio_format}`;
269
+ const finalPath = `${outputPath}.${format}`;
148
270
  await (0, promises_1.writeFile)(finalPath, new Uint8Array(audioBuffer));
149
271
  return finalPath;
150
272
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andresaya/edge-tts",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "Edge TTS is a package that allows access to the online text-to-speech service used by Microsoft Edge without the need for Microsoft Edge, Windows, or an API key.",
5
5
  "main": "dist/index.js",
6
6
  "keywords": [