@andresaya/edge-tts 1.6.1 → 1.7.0

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.
@@ -40,5 +40,46 @@ exports.Constants = {
40
40
  'Sec-Fetch-Site': 'none',
41
41
  'Sec-Fetch-Mode': 'cors',
42
42
  'Sec-Fetch-Dest': 'empty'
43
+ },
44
+ // https://learn.microsoft.com/en-us/azure/ai-services/speech-service/rest-text-to-speech?tabs=nonstreaming
45
+ OUTPUT_FORMAT: {
46
+ // Streaming
47
+ AMR_WB_16000HZ: 'amr-wb-16000hz',
48
+ AUDIO_16KHZ_16BIT_32KBPS_MONO_OPUS: 'audio-16khz-16bit-32kbps-mono-opus',
49
+ AUDIO_16KHZ_32KBITRATE_MONO_MP3: 'audio-16khz-32kbitrate-mono-mp3',
50
+ AUDIO_16KHZ_64KBITRATE_MONO_MP3: 'audio-16khz-64kbitrate-mono-mp3',
51
+ AUDIO_16KHZ_128KBITRATE_MONO_MP3: 'audio-16khz-128kbitrate-mono-mp3',
52
+ AUDIO_24KHZ_16BIT_24KBPS_MONO_OPUS: 'audio-24khz-16bit-24kbps-mono-opus',
53
+ AUDIO_24KHZ_16BIT_48KBPS_MONO_OPUS: 'audio-24khz-16bit-48kbps-mono-opus',
54
+ AUDIO_24KHZ_48KBITRATE_MONO_MP3: 'audio-24khz-48kbitrate-mono-mp3',
55
+ AUDIO_24KHZ_96KBITRATE_MONO_MP3: 'audio-24khz-96kbitrate-mono-mp3',
56
+ AUDIO_24KHZ_160KBITRATE_MONO_MP3: 'audio-24khz-160kbitrate-mono-mp3',
57
+ AUDIO_48KHZ_96KBITRATE_MONO_MP3: 'audio-48khz-96kbitrate-mono-mp3',
58
+ AUDIO_48KHZ_192KBITRATE_MONO_MP3: 'audio-48khz-192kbitrate-mono-mp3',
59
+ G722_16KHZ_64KBPS: 'g722-16khz-64kbps',
60
+ OGG_16KHZ_16BIT_MONO_OPUS: 'ogg-16khz-16bit-mono-opus',
61
+ OGG_24KHZ_16BIT_MONO_OPUS: 'ogg-24khz-16bit-mono-opus',
62
+ OGG_48KHZ_16BIT_MONO_OPUS: 'ogg-48khz-16bit-mono-opus',
63
+ RAW_8KHZ_8BIT_MONO_ALAW: 'raw-8khz-8bit-mono-alaw',
64
+ RAW_8KHZ_8BIT_MONO_MULAW: 'raw-8khz-8bit-mono-mulaw',
65
+ RAW_8KHZ_16BIT_MONO_PCM: 'raw-8khz-16bit-mono-pcm',
66
+ RAW_16KHZ_16BIT_MONO_PCM: 'raw-16khz-16bit-mono-pcm',
67
+ RAW_16KHZ_16BIT_MONO_TRUESILK: 'raw-16khz-16bit-mono-truesilk',
68
+ RAW_22050HZ_16BIT_MONO_PCM: 'raw-22050hz-16bit-mono-pcm',
69
+ RAW_24KHZ_16BIT_MONO_PCM: 'raw-24khz-16bit-mono-pcm',
70
+ RAW_24KHZ_16BIT_MONO_TRUESILK: 'raw-24khz-16bit-mono-truesilk',
71
+ RAW_44100HZ_16BIT_MONO_PCM: 'raw-44100hz-16bit-mono-pcm',
72
+ RAW_48KHZ_16BIT_MONO_PCM: 'raw-48khz-16bit-mono-pcm',
73
+ WEBM_16KHZ_16BIT_MONO_OPUS: 'webm-16khz-16bit-mono-opus',
74
+ WEBM_24KHZ_16BIT_24KBPS_MONO_OPUS: 'webm-24khz-16bit-24kbps-mono-opus',
75
+ WEBM_24KHZ_16BIT_MONO_OPUS: 'webm-24khz-16bit-mono-opus',
76
+ // NonStreaming
77
+ RIFF_8KHZ_8BIT_MONO_ALAW: 'riff-8khz-8bit-mono-alaw',
78
+ RIFF_8KHZ_8BIT_MONO_MULAW: 'riff-8khz-8bit-mono-mulaw',
79
+ RIFF_8KHZ_16BIT_MONO_PCM: 'riff-8khz-16bit-mono-pcm',
80
+ RIFF_22050HZ_16BIT_MONO_PCM: 'riff-22050hz-16bit-mono-pcm',
81
+ RIFF_24KHZ_16BIT_MONO_PCM: 'riff-24khz-16bit-mono-pcm',
82
+ RIFF_44100HZ_16BIT_MONO_PCM: 'riff-44100hz-16bit-mono-pcm',
83
+ RIFF_48KHZ_16BIT_MONO_PCM: 'riff-48khz-16bit-mono-pcm'
43
84
  }
44
85
  };
@@ -12,6 +12,7 @@ export interface SynthesisOptions {
12
12
  rate?: string | number;
13
13
  volume?: string | number;
14
14
  inputType?: 'auto' | 'ssml' | 'text';
15
+ outputFormat?: string;
15
16
  }
16
17
  export interface WordBoundary {
17
18
  type: "WordBoundary";
@@ -22,6 +23,7 @@ export interface WordBoundary {
22
23
  export declare class EdgeTTS {
23
24
  private audio_stream;
24
25
  private audio_format;
26
+ private output_format;
25
27
  private word_boundaries;
26
28
  private ws;
27
29
  getVoices(): Promise<Voice[]>;
@@ -43,6 +45,7 @@ export declare class EdgeTTS {
43
45
  private parseMetadata;
44
46
  generateSecMsGec: (trustedClientToken: string) => Promise<string>;
45
47
  getDuration(): number;
48
+ private getFileExtension;
46
49
  getAudioInfo(): {
47
50
  size: number;
48
51
  format: string;
@@ -27,6 +27,7 @@ function ensureBuffer(data) {
27
27
  class EdgeTTS {
28
28
  audio_stream = [];
29
29
  audio_format = 'mp3';
30
+ output_format = 'audio-24khz-48kbitrate-mono-mp3';
30
31
  word_boundaries = [];
31
32
  ws;
32
33
  async getVoices() {
@@ -114,38 +115,49 @@ class EdgeTTS {
114
115
  this.audio_stream = [];
115
116
  const reqId = this.generateUUID();
116
117
  const url = `${constants_1.Constants.WSS_URL}?Ocp-Apim-Subscription-Key=${constants_1.Constants.TRUSTED_CLIENT_TOKEN}&Sec-MS-GEC=${secMsGEC}&Sec-MS-GEC-Version=${constants_1.Constants.VERSION_MS_GEC}&ConnectionId=${reqId}`;
117
- console.log('WebSocket URL:', url);
118
118
  this.ws = new ws_1.default(url, {
119
119
  headers: constants_1.Constants.getBaseHeaders(),
120
120
  rejectUnauthorized: false
121
121
  });
122
122
  const SSML_text = this.getSSML(text, voice, options);
123
- const timeout = setTimeout(() => {
124
- if (this.ws && this.ws.readyState === ws_1.default.OPEN) {
125
- this.ws.close();
126
- }
127
- reject(new Error("Synthesis timeout"));
128
- }, 30000);
123
+ const outputFormat = options.outputFormat || 'audio-24khz-48kbitrate-mono-mp3';
124
+ this.output_format = outputFormat;
125
+ let timedOut = false;
126
+ let inactivityTimeout;
127
+ const resetInactivityTimeout = () => {
128
+ clearTimeout(inactivityTimeout);
129
+ inactivityTimeout = setTimeout(() => {
130
+ timedOut = true;
131
+ if (this.ws && this.ws.readyState === ws_1.default.OPEN) {
132
+ this.ws.close();
133
+ }
134
+ reject(new Error("WebSocket inactivity timeout - no response from server"));
135
+ }, 30000); // 30 seconds of inactivity
136
+ };
129
137
  this.ws.on('open', () => {
130
- const message = this.buildTTSConfigMessage();
138
+ resetInactivityTimeout(); // start the inactivity timeout
139
+ const message = this.buildTTSConfigMessage(outputFormat);
131
140
  this.ws.send(message);
132
141
  const timestamp = this.nowRFC1123();
133
142
  const speechMessage = `X-RequestId:${reqId}\r\nContent-Type:application/ssml+xml\r\nX-Timestamp:${timestamp}\r\nPath:ssml\r\n\r\n${SSML_text}`;
134
143
  this.ws.send(speechMessage);
135
144
  });
136
145
  this.ws.on('message', (data) => {
146
+ resetInactivityTimeout(); // restart inactivity timeout
137
147
  this.processAudioData(data);
138
148
  });
139
149
  this.ws.on('error', (err) => {
140
- clearTimeout(timeout);
150
+ clearTimeout(inactivityTimeout);
141
151
  if (this.ws && this.ws.readyState === ws_1.default.OPEN) {
142
152
  this.ws.close();
143
153
  }
144
154
  reject(err);
145
155
  });
146
156
  this.ws.on('close', () => {
147
- clearTimeout(timeout);
148
- resolve();
157
+ clearTimeout(inactivityTimeout);
158
+ if (!timedOut) {
159
+ resolve();
160
+ }
149
161
  });
150
162
  });
151
163
  }
@@ -245,10 +257,10 @@ class EdgeTTS {
245
257
  parseRFC1123(rfcStr) {
246
258
  return new Date(rfcStr);
247
259
  }
248
- buildTTSConfigMessage() {
260
+ buildTTSConfigMessage(outputFormat = 'audio-24khz-48kbitrate-mono-mp3') {
249
261
  const timestamp = this.nowRFC1123();
250
262
  return `X-Timestamp:${timestamp}\r\nContent-Type:application/json; charset=utf-8\r\nPath:speech.config\r\n\r\n` +
251
- `{"context":{"synthesis":{"audio":{"metadataoptions":{"sentenceBoundaryEnabled":false,"wordBoundaryEnabled":true},"outputFormat":"audio-24khz-48kbitrate-mono-mp3"}}}}`;
263
+ `{"context":{"synthesis":{"audio":{"metadataoptions":{"sentenceBoundaryEnabled":false,"wordBoundaryEnabled":true},"outputFormat":"${outputFormat}"}}}}`;
252
264
  }
253
265
  async *synthesizeStream(text, voice = 'en-US-AnaNeural', options = {}) {
254
266
  this.audio_stream = [];
@@ -260,6 +272,8 @@ class EdgeTTS {
260
272
  rejectUnauthorized: false
261
273
  });
262
274
  const SSML_text = this.getSSML(text, voice, options);
275
+ const outputFormat = options.outputFormat || 'audio-24khz-48kbitrate-mono-mp3';
276
+ this.output_format = outputFormat;
263
277
  const queue = [];
264
278
  let done = false;
265
279
  let error = null;
@@ -271,19 +285,33 @@ class EdgeTTS {
271
285
  notify = null;
272
286
  }
273
287
  };
274
- const timeout = setTimeout(() => {
275
- if (this.ws && this.ws.readyState === ws_1.default.OPEN) {
276
- this.ws.close();
277
- }
278
- }, 30000);
288
+ let timedOut = false;
289
+ let inactivityTimeout;
290
+ const resetInactivityTimeout = () => {
291
+ clearTimeout(inactivityTimeout);
292
+ inactivityTimeout = setTimeout(() => {
293
+ timedOut = true;
294
+ error = new Error("WebSocket inactivity timeout - no response from server");
295
+ done = true;
296
+ if (this.ws && this.ws.readyState === ws_1.default.OPEN) {
297
+ this.ws.close();
298
+ }
299
+ if (notify) {
300
+ notify();
301
+ notify = null;
302
+ }
303
+ }, 30000); // 30 seconds of inactivity
304
+ };
279
305
  this.ws.on('open', () => {
280
- const message = this.buildTTSConfigMessage();
306
+ resetInactivityTimeout(); // start the inactivity timeout
307
+ const message = this.buildTTSConfigMessage(outputFormat);
281
308
  this.ws.send(message);
282
309
  const timestamp = this.nowRFC1123();
283
310
  const speechMessage = `X-RequestId:${reqId}\r\nContent-Type:application/ssml+xml\r\nX-Timestamp:${timestamp}\r\nPath:ssml\r\n\r\n${SSML_text}`;
284
311
  this.ws.send(speechMessage);
285
312
  });
286
313
  this.ws.on('message', (data) => {
314
+ resetInactivityTimeout(); // restart inactivity timeout
287
315
  const buffer = ensureBuffer(data);
288
316
  const needle = buffer_1.Buffer.from('Path:audio\r\n');
289
317
  const audioStartIndex = buffer.indexOf(new Uint8Array(needle));
@@ -307,6 +335,7 @@ class EdgeTTS {
307
335
  }
308
336
  });
309
337
  this.ws.on('error', (err) => {
338
+ clearTimeout(inactivityTimeout);
310
339
  error = err;
311
340
  done = true;
312
341
  if (notify) {
@@ -315,7 +344,7 @@ class EdgeTTS {
315
344
  }
316
345
  });
317
346
  this.ws.on('close', () => {
318
- clearTimeout(timeout);
347
+ clearTimeout(inactivityTimeout);
319
348
  done = true;
320
349
  if (notify) {
321
350
  notify();
@@ -405,17 +434,41 @@ class EdgeTTS {
405
434
  const estimatedDuration = bufferSize / (24000 * 3); // 24000 Hz sample rate, 3 bytes per sample (16-bit stereo)
406
435
  return estimatedDuration;
407
436
  }
437
+ getFileExtension(format) {
438
+ if (format.includes('mp3'))
439
+ return 'mp3';
440
+ if (format.includes('opus') && format.includes('webm'))
441
+ return 'webm';
442
+ if (format.includes('opus') && format.includes('ogg'))
443
+ return 'ogg';
444
+ if (format.includes('wav') || format.includes('riff'))
445
+ return 'wav';
446
+ if (format.includes('pcm') && format.includes('raw'))
447
+ return 'pcm';
448
+ if (format.includes('alaw'))
449
+ return 'alaw';
450
+ if (format.includes('mulaw'))
451
+ return 'mulaw';
452
+ if (format.includes('truesilk'))
453
+ return 'silk';
454
+ if (format.includes('g722'))
455
+ return 'g722';
456
+ if (format.includes('amr'))
457
+ return 'amr';
458
+ return 'audio';
459
+ }
408
460
  getAudioInfo() {
409
461
  const buffer = this.toBuffer();
410
462
  return {
411
463
  size: buffer.length,
412
- format: this.audio_format,
464
+ format: this.getFileExtension(this.output_format),
413
465
  estimatedDuration: this.getDuration()
414
466
  };
415
467
  }
416
- async toFile(outputPath, format = this.audio_format) {
417
- if (!format || typeof format !== 'string')
418
- format = this.audio_format;
468
+ async toFile(outputPath, format) {
469
+ if (!format) {
470
+ format = this.getFileExtension(this.output_format);
471
+ }
419
472
  const audioBuffer = this.toBuffer();
420
473
  const finalPath = `${outputPath}.${format}`;
421
474
  await (0, promises_1.writeFile)(finalPath, new Uint8Array(audioBuffer));
package/package.json CHANGED
@@ -1,54 +1,54 @@
1
- {
2
- "name": "@andresaya/edge-tts",
3
- "version": "1.6.1",
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
- "main": "dist/index.js",
6
- "browser": "dist/browser/edge-tts.esm.js",
7
- "unpkg": "dist/browser/edge-tts.umd.min.js",
8
- "jsdelivr": "dist/browser/edge-tts.umd.min.js",
9
- "keywords": [
10
- "edge",
11
- "tts",
12
- "text-to-speech",
13
- "microsoft",
14
- "microsoft edge",
15
- "speech-synthesis"
16
- ],
17
- "author": "Andres Aya",
18
- "license": "GPL-3.0-only",
19
- "homepage": "https://github.com/andresayac/edge-tts",
20
- "bugs": {
21
- "url": "https://github.com/andresayac/edge-tts/issues"
22
- },
23
- "scripts": {
24
- "build": "tsc",
25
- "build:bin": "bun scripts/build.ts",
26
- "build:browser": "bun scripts/build-browser.ts",
27
- "prepublishOnly": "npm run build && npm run build:bin && npm run build:browser"
28
- },
29
- "devDependencies": {
30
- "@types/bun": "latest",
31
- "@types/ws": "^8.18.1",
32
- "esbuild": "^0.25.12",
33
- "typescript": "^5.6.2"
34
- },
35
- "dependencies": {
36
- "@types/uuid": "^10.0.0",
37
- "commander": "^12.1.0",
38
- "events": "^3.3.0",
39
- "fs": "^0.0.1-security",
40
- "ws": "^8.18.3"
41
- },
42
- "peerDependencies": {
43
- "typescript": "^5.0.0"
44
- },
45
- "files": [
46
- "dist"
47
- ],
48
- "bin": {
49
- "edge-tts": "./dist/cli/edge-tts.js"
50
- },
51
- "engines": {
52
- "bun": ">=0.5.0"
53
- }
54
- }
1
+ {
2
+ "name": "@andresaya/edge-tts",
3
+ "version": "1.7.0",
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
+ "main": "dist/index.js",
6
+ "browser": "dist/browser/edge-tts.esm.js",
7
+ "unpkg": "dist/browser/edge-tts.umd.min.js",
8
+ "jsdelivr": "dist/browser/edge-tts.umd.min.js",
9
+ "keywords": [
10
+ "edge",
11
+ "tts",
12
+ "text-to-speech",
13
+ "microsoft",
14
+ "microsoft edge",
15
+ "speech-synthesis"
16
+ ],
17
+ "author": "Andres Aya",
18
+ "license": "GPL-3.0-only",
19
+ "homepage": "https://github.com/andresayac/edge-tts",
20
+ "bugs": {
21
+ "url": "https://github.com/andresayac/edge-tts/issues"
22
+ },
23
+ "scripts": {
24
+ "build": "tsc",
25
+ "build:bin": "bun scripts/build.ts",
26
+ "build:browser": "bun scripts/build-browser.ts",
27
+ "prepublishOnly": "npm run build && npm run build:bin && npm run build:browser"
28
+ },
29
+ "devDependencies": {
30
+ "@types/bun": "latest",
31
+ "@types/ws": "^8.18.1",
32
+ "esbuild": "^0.25.12",
33
+ "typescript": "^5.6.2"
34
+ },
35
+ "dependencies": {
36
+ "@types/uuid": "^10.0.0",
37
+ "commander": "^12.1.0",
38
+ "events": "^3.3.0",
39
+ "fs": "^0.0.1-security",
40
+ "ws": "^8.18.3"
41
+ },
42
+ "peerDependencies": {
43
+ "typescript": "^5.0.0"
44
+ },
45
+ "files": [
46
+ "dist"
47
+ ],
48
+ "bin": {
49
+ "edge-tts": "./dist/cli/edge-tts.js"
50
+ },
51
+ "engines": {
52
+ "bun": ">=0.5.0"
53
+ }
54
+ }
package/dist/browser.d.ts DELETED
@@ -1 +0,0 @@
1
- export { EdgeTTS, type Voice, type SynthesisOptions, type WordBoundary } from './browser/EdgeTTS.browser';
package/dist/browser.js DELETED
@@ -1,5 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.EdgeTTS = void 0;
4
- var EdgeTTS_browser_1 = require("./browser/EdgeTTS.browser");
5
- Object.defineProperty(exports, "EdgeTTS", { enumerable: true, get: function () { return EdgeTTS_browser_1.EdgeTTS; } });