@andresaya/edge-tts 1.8.1 → 1.9.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.
package/README.md CHANGED
@@ -7,13 +7,16 @@
7
7
 
8
8
  - **Text-to-Speech**: Convert text into natural-sounding speech using Microsoft Edge's TTS capabilities.
9
9
  - **TypeScript Support**: Full TypeScript support with comprehensive type definitions included.
10
- - **Multiple Audio Formats**: Support for 36+ audio formats (MP3, WebM, OGG, WAV, PCM, and more).
10
+ - **Audio Formats**: MP3 at 48 or 96 kbit/s, and WebM/Opus.
11
11
  - **Multiple Voices**: Access a variety of voices to suit your project's needs.
12
12
  - **Voice Filtering**: Filter voices by language and gender for better selection.
13
13
  - **Audio Information**: Get detailed information about generated audio (size, duration, format).
14
14
  - **Audio Export Options**: Export synthesized audio in different formats (raw, base64, or directly to a file).
15
15
  - **Streaming Support**: Stream audio data in real-time for better performance.
16
- - **Word Boundaries Metadata**: Get word boundary information with precise timestamps.
16
+ - **Word and Sentence Boundaries**: Get word and sentence timings with precise timestamps.
17
+ - **Subtitles**: Generate SRT or WebVTT subtitles, one cue per sentence.
18
+ - **Concurrent Use**: `generate()` returns its own result, so one instance can run several syntheses at once.
19
+ - **Configurable**: Override the endpoint, headers, timeout and voice cache without waiting for a release.
17
20
  - **Command-Line Interface**: Use a simple CLI for easy access to functionality.
18
21
  - **Easy Integration**: Modular structure allows for easy inclusion in existing projects.
19
22
 
@@ -28,6 +31,8 @@ bun add @andresaya/edge-tts
28
31
  npm install @andresaya/edge-tts
29
32
  ```
30
33
 
34
+ Tested on Node.js 18, 20 and 24, and on Bun.
35
+
31
36
  ## TypeScript Support
32
37
 
33
38
  Edge TTS is written in **TypeScript** and includes full type definitions. No additional `@types` packages are needed.
@@ -37,20 +42,29 @@ Edge TTS is written in **TypeScript** and includes full type definitions. No add
37
42
  ```typescript
38
43
  import {
39
44
  EdgeTTS,
45
+ EdgeTTSError,
40
46
  Constants,
41
47
  Voice,
42
48
  SynthesisOptions,
43
- WordBoundary
49
+ SynthesisResult,
50
+ EdgeTTSOptions,
51
+ WordBoundary,
52
+ SentenceBoundary
44
53
  } from '@andresaya/edge-tts';
45
54
 
46
55
  // Voice interface
47
56
  interface Voice {
48
57
  Name: string;
49
58
  ShortName: string;
50
- Gender: 'Male' | 'Female';
59
+ Gender: string; // 'Male' | 'Female'
51
60
  Locale: string;
52
61
  FriendlyName: string;
53
62
  LocalName: string;
63
+ DisplayName?: string;
64
+ LocaleName?: string;
65
+ VoiceType?: string;
66
+ SecondaryLocaleList?: string[];
67
+ VoiceTag?: { TailoredScenarios: string[]; VoicePersonalities: string[] };
54
68
  }
55
69
 
56
70
  // Synthesis options
@@ -59,15 +73,34 @@ interface SynthesisOptions {
59
73
  rate?: string | number; // e.g., '50%' or 50
60
74
  volume?: string | number; // e.g., '90%' or 90
61
75
  outputFormat?: string; // e.g., Constants.OUTPUT_FORMAT.AUDIO_24KHZ_96KBITRATE_MONO_MP3
76
+ signal?: AbortSignal; // cancels the synthesis
77
+ timeout?: number; // inactivity timeout in ms for this call
62
78
  }
63
79
 
64
- // Word boundary metadata
80
+ // Word and sentence boundary metadata (offset and duration in 100 ns ticks)
65
81
  interface WordBoundary {
66
82
  type: "WordBoundary";
67
83
  offset: number;
68
84
  duration: number;
69
85
  text: string;
70
86
  }
87
+
88
+ interface SentenceBoundary {
89
+ type: "SentenceBoundary";
90
+ offset: number;
91
+ duration: number;
92
+ text: string;
93
+ }
94
+
95
+ // Returned by generate()
96
+ interface SynthesisResult {
97
+ audio: Uint8Array; // a Buffer in Node.js
98
+ format: string; // file extension: 'mp3' or 'webm'
99
+ outputFormat: string;
100
+ wordBoundaries: WordBoundary[];
101
+ sentenceBoundaries: SentenceBoundary[];
102
+ duration: number; // estimated, in seconds
103
+ }
71
104
  ```
72
105
 
73
106
  ### Type-Safe Usage Example
@@ -105,9 +138,19 @@ To synthesize speech from text:
105
138
  edge-tts synthesize -t "Hello, world!" -o hello_world_audio
106
139
  ```
107
140
 
108
- To list available voices:
141
+ Other options:
142
+ ```bash
143
+ # Choose the voice, read the text from a file, write subtitles too
144
+ edge-tts synthesize -f story.txt -v es-CO-SalomeNeural -o story.mp3 --subtitles story.srt
145
+
146
+ # Read from stdin, WebM/Opus output, WebVTT subtitles
147
+ cat story.txt | edge-tts synthesize -f - --format webm-24khz-16bit-mono-opus -o story --subtitles story.vtt
148
+ ```
149
+
150
+ To list available voices, optionally filtered:
109
151
  ```bash
110
152
  edge-tts voice-list
153
+ edge-tts voice-list --locale es-CO --gender female
111
154
  ```
112
155
 
113
156
  ### Integration into Your Project
@@ -119,6 +162,23 @@ import { EdgeTTS } from '@andresaya/edge-tts';
119
162
  const tts = new EdgeTTS();
120
163
  ```
121
164
 
165
+ ### Configuration
166
+
167
+ Every option is optional. The defaults come from `Constants`, so when Microsoft changes the service you can point the library at the new values yourself instead of waiting for a release.
168
+
169
+ ```js
170
+ const tts = new EdgeTTS({
171
+ wssUrl: 'wss://api.msedgeservices.com/tts/cognitiveservices/websocket/v1',
172
+ voicesUrl: 'https://speech.platform.bing.com/consumer/speech/synthesize/readaloud/voices/list',
173
+ secMsGecVersion: '1-155.0.4283.45',
174
+ headers: { 'User-Agent': '...' }, // Node.js only, added to the default headers
175
+ timeout: 30000, // ms without data before giving up
176
+ voicesCacheTtl: 600000, // ms to reuse the voice list, 0 to always fetch it
177
+ });
178
+ ```
179
+
180
+ If the computer's clock is off, the service refuses the connection. In Node.js the library then reads the time from the server and retries once.
181
+
122
182
  ## API Reference
123
183
 
124
184
  ### Voice Management
@@ -186,9 +246,26 @@ await tts.synthesize("Hello, world!", 'en-US-AriaNeural', {
186
246
  });
187
247
  ```
188
248
 
249
+ #### Independent Results with generate()
250
+
251
+ `synthesize()` keeps the audio on the instance, so two calls at the same time on one instance overwrite each other. `generate()` returns everything in its own result instead and leaves the instance alone:
252
+
253
+ ```js
254
+ const [hello, bye] = await Promise.all([
255
+ tts.generate("Hello!", 'en-US-AriaNeural'),
256
+ tts.generate("Goodbye!", 'en-US-AriaNeural'),
257
+ ]);
258
+
259
+ hello.audio; // Buffer in Node.js, Uint8Array in the browser
260
+ hello.format; // 'mp3'
261
+ hello.duration; // estimated seconds
262
+ hello.wordBoundaries; // WordBoundary[]
263
+ hello.sentenceBoundaries; // SentenceBoundary[]
264
+ ```
265
+
189
266
  #### Audio Output Formats
190
267
 
191
- Edge TTS supports multiple audio formats. You can specify the format using the `outputFormat` option:
268
+ You can specify the format using the `outputFormat` option:
192
269
 
193
270
  ```js
194
271
  import { EdgeTTS, Constants } from '@andresaya/edge-tts';
@@ -206,28 +283,17 @@ await tts.synthesize("Hello!", 'en-US-AriaNeural', {
206
283
  outputFormat: Constants.OUTPUT_FORMAT.WEBM_24KHZ_16BIT_MONO_OPUS
207
284
  });
208
285
  await tts.toFile('./output/audio'); // Automatically saved as .webm
209
-
210
- // Lossless WAV
211
- await tts.synthesize("Hello!", 'en-US-AriaNeural', {
212
- outputFormat: Constants.OUTPUT_FORMAT.RIFF_24KHZ_16BIT_MONO_PCM
213
- });
214
- await tts.toFile('./output/audio'); // Automatically saved as .wav
215
286
  ```
216
287
 
217
- **Available formats (all 36 tested and compatible):**
218
-
219
- - **MP3 Formats** (Streaming): 16kHz, 24kHz, 48kHz with various bitrates (32-192 kbps)
220
- - **Opus Formats** (Streaming): Audio, WebM, and OGG containers
221
- - **WAV/PCM Formats** (Non-streaming): RIFF (8-48 kHz) and RAW variants
222
- - **Specialized Codecs**: AMR-WB, G.722, TrueSilk, A-law, μ-law
288
+ **Available formats:**
223
289
 
224
- See `Constants.OUTPUT_FORMAT` for the complete list. The file extension is automatically detected based on the format.
290
+ | Constant | Value |
291
+ |----------|-------|
292
+ | `AUDIO_24KHZ_48KBITRATE_MONO_MP3` (default) | `audio-24khz-48kbitrate-mono-mp3` |
293
+ | `AUDIO_24KHZ_96KBITRATE_MONO_MP3` | `audio-24khz-96kbitrate-mono-mp3` |
294
+ | `WEBM_24KHZ_16BIT_MONO_OPUS` | `webm-24khz-16bit-mono-opus` |
225
295
 
226
- **Format recommendations:**
227
- - 🌐 **Web streaming**: `WEBM_24KHZ_16BIT_MONO_OPUS` or `AUDIO_24KHZ_96KBITRATE_MONO_MP3`
228
- - 📱 **Mobile apps**: `AUDIO_24KHZ_48KBITRATE_MONO_MP3`
229
- - 💾 **High quality**: `AUDIO_48KHZ_192KBITRATE_MONO_MP3` or `RIFF_48KHZ_16BIT_MONO_PCM`
230
- - ⚡ **Low bandwidth**: `AUDIO_16KHZ_32KBITRATE_MONO_MP3`
296
+ The service returns no audio for other formats, such as WAV, PCM or other MP3 bitrates.
231
297
 
232
298
 
233
299
  #### Streaming Synthesis
@@ -239,6 +305,22 @@ for await (const chunk of tts.synthesizeStream("Long text to stream...", 'en-US-
239
305
  }
240
306
  ```
241
307
 
308
+ Leaving the loop early with `break` closes the connection.
309
+
310
+ #### Cancelling and Timeouts
311
+ ```js
312
+ const controller = new AbortController();
313
+ setTimeout(() => controller.abort(), 5000);
314
+
315
+ try {
316
+ await tts.synthesize("Hello!", 'en-US-AriaNeural', { signal: controller.signal, timeout: 10000 });
317
+ } catch (error) {
318
+ if (error.name === 'AbortError') console.log('Cancelled');
319
+ }
320
+ ```
321
+
322
+ `signal` works with `synthesize()`, `synthesizeStream()` and `generate()`. `timeout` is how long to wait without receiving data, 30 seconds by default.
323
+
242
324
  ### Audio Information
243
325
 
244
326
  #### Get Audio Details
@@ -277,16 +359,39 @@ const buffer = tts.toBuffer(); // Get as Buffer object
277
359
  const filePath = await tts.toFile("output_audio");
278
360
  console.log(`Audio saved to: ${filePath}`);
279
361
  // Creates: output_audio.mp3
362
+
363
+ await tts.toFile("speech.mp3"); // Creates: speech.mp3 (the extension is not repeated)
280
364
  ```
281
365
 
282
- ### Word Boundaries Metadata
366
+ ### Word and Sentence Boundaries
367
+
368
+ Offsets and durations are in 100-nanosecond ticks (10,000,000 per second).
369
+
370
+ ```js
371
+ await tts.synthesize("Hello, world. How are you?", 'en-US-AriaNeural');
283
372
 
284
- ```php
285
- // Get word boundaries with timestamps
286
- $boundaries = $tts->getWordBoundaries();
373
+ const words = tts.getWordBoundaries(); // [{ type: 'WordBoundary', offset, duration, text: 'Hello' }, ...]
374
+ const sentences = tts.getSentenceBoundaries(); // [{ type: 'SentenceBoundary', offset, duration, text: 'Hello, world.' }, ...]
287
375
 
288
- // Save metadata to file
289
- $tts->saveMetadata('metadata.json');
376
+ // Save word boundaries to a JSON file
377
+ await tts.saveMetadata('metadata.json');
378
+ ```
379
+
380
+ ### Subtitles
381
+
382
+ One cue per sentence, with the punctuation of the original text.
383
+
384
+ ```js
385
+ import { writeFile } from 'fs/promises';
386
+ import { toSRT, toVTT } from '@andresaya/edge-tts';
387
+
388
+ await tts.synthesize("¿Hola? Esto es una prueba.", 'es-CO-SalomeNeural');
389
+ await writeFile('speech.srt', tts.toSRT());
390
+ await writeFile('speech.vtt', tts.toVTT());
391
+
392
+ // From a generate() result
393
+ const result = await tts.generate("Hello, world.", 'en-US-AriaNeural');
394
+ await writeFile('hello.srt', toSRT(result.sentenceBoundaries));
290
395
  ```
291
396
 
292
397
 
@@ -396,7 +501,7 @@ exploreVoices().catch(console.error);
396
501
 
397
502
  This library can be used directly in web browsers via CDN or ES modules.
398
503
 
399
- **⚠️ 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!
504
+ **⚠️ Important:** Connecting directly, synthesis only works in **Microsoft Edge**. The speech service refuses connections whose user agent is not Edge, and a web page cannot change the user agent its browser sends. Chrome, Firefox and Safari get a 403 when synthesizing. For those browsers, use a relay (see [Other Browsers](#other-browsers-relay)). The Node.js version is not affected.
400
505
 
401
506
  ### CDN Usage (UMD)
402
507
 
@@ -470,6 +575,23 @@ This library can be used directly in web browsers via CDN or ES modules.
470
575
  </script>
471
576
  ```
472
577
 
578
+ ### Other Browsers (Relay)
579
+
580
+ A small relay running in Node.js connects to the service with Edge's headers and passes the messages through. Start the example relay:
581
+
582
+ ```bash
583
+ node examples/relay-server.mjs # ws://127.0.0.1:8080
584
+ ```
585
+
586
+ And point the browser at it:
587
+
588
+ ```js
589
+ const tts = new EdgeTTS({ wssUrl: 'ws://127.0.0.1:8080' });
590
+ await tts.synthesize("Hello from Chrome!", 'en-US-AriaNeural');
591
+ ```
592
+
593
+ The example only listens on `127.0.0.1`. If you deploy it, put it behind your own authentication or origin check so it is not open to everyone.
594
+
473
595
  ### Complete Browser Example
474
596
 
475
597
  For a full working example with voice selection and synthesis, see [`examples/browser-standalone.html`](examples/browser-standalone.html).
@@ -500,30 +622,38 @@ For a full working example with voice selection and synthesis, see [`examples/br
500
622
 
501
623
  ## Error Handling
502
624
 
625
+ Errors raised by the library are `EdgeTTSError` instances with a `code`. Network errors from Node.js (such as `ECONNREFUSED`) are passed through unchanged.
626
+
627
+ | `code` | When |
628
+ |--------|------|
629
+ | `HANDSHAKE` | The service refused the connection. `status` holds the HTTP status in Node.js (usually 403). |
630
+ | `NETWORK` | The connection failed after it was open (browser). |
631
+ | `TIMEOUT` | No data arrived for `timeout` ms. |
632
+ | `NO_AUDIO` | No synthesis yet, or the service returned no audio (voice and text in different scripts, or an unsupported format). |
633
+ | `INVALID_OPTION` | `pitch`, `rate` or `volume` is not valid. |
634
+ | `VOICES` | The voice list request failed. `status` holds the HTTP status. |
635
+
636
+ Cancelling with an `AbortSignal` rejects with the signal's reason, an `AbortError`.
637
+
503
638
  ```js
504
- import { EdgeTTS } from '@andresaya/edge-tts';
639
+ import { EdgeTTS, EdgeTTSError } from '@andresaya/edge-tts';
505
640
 
506
641
  async function handleErrors() {
507
642
  const tts = new EdgeTTS();
508
643
 
509
644
  try {
510
- await tts.synthesize("Test text", 'invalid-voice-name');
645
+ await tts.synthesize("Test", 'en-US-AriaNeural', { volume: -150 });
511
646
  } catch (error) {
512
- console.error('Synthesis failed:', error.message);
647
+ if (error instanceof EdgeTTSError && error.code === 'INVALID_OPTION') {
648
+ console.error('Invalid parameter:', error.message);
649
+ }
513
650
  }
514
651
 
515
652
  try {
516
653
  // This will throw an error - no audio data
517
654
  const duration = tts.getDuration();
518
655
  } catch (error) {
519
- console.error('No audio data available:', error.message);
520
- }
521
-
522
- try {
523
- // Invalid volume range
524
- await tts.synthesize("Test", 'en-US-AriaNeural', { volume: -150 });
525
- } catch (error) {
526
- console.error('Invalid parameter:', error.message);
656
+ console.error(error.code, error.message); // NO_AUDIO ...
527
657
  }
528
658
  }
529
659
  ```