@andresaya/edge-tts 1.8.3 → 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 +173 -43
- package/dist/browser/edge-tts.esm.js +487 -287
- package/dist/browser/edge-tts.esm.js.map +4 -4
- package/dist/browser/edge-tts.esm.min.js +20 -16
- package/dist/browser/edge-tts.esm.min.js.map +4 -4
- package/dist/browser/edge-tts.umd.js +487 -287
- package/dist/browser/edge-tts.umd.js.map +4 -4
- package/dist/browser/edge-tts.umd.min.js +20 -16
- package/dist/browser/edge-tts.umd.min.js.map +4 -4
- package/dist/cli/edge-tts.js +2 -1
- package/dist/commands/SynthesizeCommand.js +19 -3
- package/dist/commands/VoiceListCommand.js +6 -2
- package/dist/config/constants.js +2 -1
- package/dist/core/base.d.ts +33 -0
- package/dist/core/base.js +187 -0
- package/dist/core/clock.d.ts +3 -0
- package/dist/core/clock.js +20 -0
- package/dist/core/crypto.d.ts +8 -0
- package/dist/core/crypto.js +14 -0
- package/dist/core/errors.d.ts +9 -0
- package/dist/core/errors.js +15 -0
- package/dist/core/protocol.d.ts +23 -0
- package/dist/core/protocol.js +206 -0
- package/dist/core/session.d.ts +24 -0
- package/dist/core/session.js +87 -0
- package/dist/core/subtitles.d.ts +7 -0
- package/dist/core/subtitles.js +24 -0
- package/dist/core/types.d.ts +53 -0
- package/dist/core/types.js +2 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +6 -1
- package/dist/services/EdgeTTS.d.ts +18 -41
- package/dist/services/EdgeTTS.js +79 -376
- package/package.json +3 -2
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
|
-
- **
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
285
|
-
//
|
|
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
|
|
289
|
-
|
|
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:**
|
|
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
|
|
645
|
+
await tts.synthesize("Test", 'en-US-AriaNeural', { volume: -150 });
|
|
511
646
|
} catch (error) {
|
|
512
|
-
|
|
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(
|
|
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
|
```
|