@andresaya/edge-tts 1.3.1 → 1.4.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
@@ -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
 
@@ -10,8 +11,10 @@
10
11
  - **Audio Information**: Get detailed information about generated audio (size, duration, format).
11
12
  - **Audio Export Options**: Export synthesized audio in different formats (raw, base64, or directly to a file).
12
13
  - **Streaming Support**: Stream audio data in real-time for better performance.
14
+ - **Word Boundaries Metadata**: Get word boundary information with precise timestamps.
13
15
  - **Command-Line Interface**: Use a simple CLI for easy access to functionality.
14
16
  - **Easy Integration**: Modular structure allows for easy inclusion in existing projects.
17
+ - **SSML Custom**: 🥳🥳 Edge TTS accepts raw SSML with all characteristics of Azure AI Speech.
15
18
 
16
19
  ## Installation
17
20
 
@@ -39,6 +42,11 @@ To synthesize speech from text:
39
42
  edge-tts synthesize -t "Hello, world!" -o hello_world_audio
40
43
  ```
41
44
 
45
+ From file in format SSML:
46
+ ```bash
47
+ edge-tts synthesize -f ssml.txt --ssml -o salida
48
+ ```
49
+
42
50
  To list available voices:
43
51
  ```bash
44
52
  edge-tts voice-list
@@ -83,6 +91,49 @@ const maleVoices = await tts.getVoicesByGender('Male');
83
91
 
84
92
  ### Text Synthesis
85
93
 
94
+ #### Custom SSML (Advanced)
95
+ Edge TTS accepts raw SSML so you can control prosody, styles, pauses, pronunciations, and more. You can pass SSML from code or the CLI. By default the library auto-detects if your input is SSML; you can also force the mode.
96
+
97
+ More information
98
+ [Azure AI Speech](https://learn.microsoft.com/en-us/azure/ai-services/speech-service/speech-synthesis-markup-structure)
99
+
100
+ ### SSML-builder
101
+ [@andresaya/ssml-builder](https://www.npmjs.com/package/@andresaya/ssml-builder) A powerful, type-safe TypeScript library for building Speech Synthesis Markup Language (SSML) documents. Create expressive text-to-speech applications with Azure Speech Service and other SSML-compliant engines.
102
+
103
+ #### What the library does for you
104
+
105
+ - Auto-detect / force mode: options.inputType may be 'auto' | 'ssml' | 'text' (default: auto).
106
+ - Validation: Throws helpful errors if SSML is malformed (e.g., missing <speak>, <voice>, or the synthesis namespace).
107
+ - Voice injection: If your SSML lacks <voice>, it injects one with the voice you passed.
108
+ - Text wrapping: If you pass plain text (or inputType: 'text'), it wraps it in a valid SSML envelope using your rate, pitch, and volume.
109
+
110
+ ```js
111
+ import { EdgeTTS } from '@andresaya/edge-tts';
112
+
113
+ const tts = new EdgeTTS();
114
+
115
+ const ssml = `
116
+ <speak version="1.0"
117
+ xmlns="http://www.w3.org/2001/10/synthesis"
118
+ xmlns:mstts="https://www.w3.org/2001/mstts"
119
+ xml:lang="es-CO">
120
+ <voice name="es-CO-GonzaloNeural">
121
+ <mstts:express-as style="narration-professional">
122
+ <prosody rate="+5%" pitch="+10Hz" volume="+0%">
123
+ Hola, este es un ejemplo de <emphasis>SSML</emphasis>.
124
+ <break time="400ms" />
125
+ El número es <say-as interpret-as="cardinal">2025</say-as>.
126
+ La palabra se pronuncia
127
+ <phoneme alphabet="ipa" ph="ˈxola">hola</phoneme>.
128
+ </prosody>
129
+ </mstts:express-as>
130
+ </voice>
131
+ </speak>`.trim();
132
+
133
+ // Auto-detects SSML, or force it with inputType: 'ssml'
134
+ await tts.synthesize(ssml, 'es-CO-GonzaloNeural', { inputType: 'ssml' });
135
+ ```
136
+
86
137
  #### Basic Synthesis
87
138
  ```js
88
139
  // Simple synthesis with default voice
@@ -150,6 +201,17 @@ console.log(`Audio saved to: ${filePath}`);
150
201
  // Creates: output_audio.mp3
151
202
  ```
152
203
 
204
+ ### Word Boundaries Metadata
205
+
206
+ ```php
207
+ // Get word boundaries with timestamps
208
+ $boundaries = $tts->getWordBoundaries();
209
+
210
+ // Save metadata to file
211
+ $tts->saveMetadata('metadata.json');
212
+ ```
213
+
214
+
153
215
  ## Examples
154
216
 
155
217
  ### Complete Example with Voice Selection
@@ -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.3.0')
9
+ .version('1.4.0')
10
10
  .addCommand(SynthesizeCommand_1.SynthesizeCommand)
11
11
  .addCommand(VoiceListCommand_1.VoiceListCommand);
12
12
  try {
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SynthesizeCommand = void 0;
4
4
  const commander_1 = require("commander");
5
5
  const EdgeTTS_1 = require("../services/EdgeTTS");
6
+ const promises_1 = require("fs/promises");
6
7
  exports.SynthesizeCommand = new commander_1.Command('synthesize')
7
8
  .description('Edge TTS: synthesize text to audio')
8
9
  .option('-t, --text <text>', 'Text to convert to audio')
@@ -11,14 +12,36 @@ exports.SynthesizeCommand = new commander_1.Command('synthesize')
11
12
  .option('-l, --volume [volume]', 'Volume of speech', '0%')
12
13
  .option('-p, --pitch [pitch]', 'Pitch of speech', '0Hz')
13
14
  .option('-o, --output [output]', 'Output file name', `output_${Date.now()}`)
15
+ .option('-f, --file [file]', 'Input file (text or SSML)')
16
+ .option('--ssml', 'Force interpret input as SSML (auto-detect by default)')
14
17
  .action(async (options) => {
15
- const { text, voice, pitch, rate, volume, output } = options;
16
- if (!text) {
17
- console.error('Text is required');
18
+ const { text, voice, pitch, rate, volume, output, file, ssml: forceSSML } = options;
19
+ if (!text && !file) {
20
+ console.error('Error: Text (-t) or file (-f) is required');
18
21
  process.exit(1);
19
22
  }
23
+ if (text && file) {
24
+ console.error('Error: Cannot use both text (-t) and file (-f) options');
25
+ process.exit(1);
26
+ }
27
+ let content = text;
28
+ if (file) {
29
+ try {
30
+ content = await (0, promises_1.readFile)(file, 'utf-8');
31
+ console.log(`Reading from file: ${file}`);
32
+ }
33
+ catch (error) {
34
+ console.error(`Error reading file: ${error}`);
35
+ process.exit(1);
36
+ }
37
+ }
20
38
  const tts = new EdgeTTS_1.EdgeTTS();
21
- await tts.synthesize(text, voice, { pitch, rate, volume });
22
- await tts.toFile(`${output}`);
23
- console.log(`Audio file generated: ${output}.mp3`);
39
+ await tts.synthesize(content, voice, {
40
+ pitch,
41
+ rate,
42
+ volume,
43
+ inputType: forceSSML ? 'ssml' : 'auto'
44
+ });
45
+ const savedPath = await tts.toFile(output);
46
+ console.log(`Audio file generated: ${savedPath}`);
24
47
  });
@@ -2,4 +2,6 @@ export declare const Constants: {
2
2
  TRUSTED_CLIENT_TOKEN: string;
3
3
  WSS_URL: string;
4
4
  VOICES_URL: string;
5
+ USER_AGENT: string;
6
+ VERSION_MS_GEC: string;
5
7
  };
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Constants = void 0;
4
4
  exports.Constants = {
5
5
  TRUSTED_CLIENT_TOKEN: '6A5AA1D4EAFF4E9FB37E23D68491D6F4',
6
- WSS_URL: 'wss://speech.platform.bing.com/consumer/speech/synthesize/readaloud/edge/v1',
7
- VOICES_URL: 'https://speech.platform.bing.com/consumer/speech/synthesize/readaloud/voices/list',
6
+ WSS_URL: 'wss://api.msedgeservices.com/tts/cognitiveservices/websocket/v1',
7
+ VOICES_URL: 'https://api.msedgeservices.com/tts/cognitiveservices/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',
9
+ VERSION_MS_GEC: '1-140.0.3485.14'
8
10
  };
@@ -5,15 +5,24 @@ export interface Voice {
5
5
  Gender: string;
6
6
  Locale: string;
7
7
  FriendlyName: string;
8
+ LocalName: string;
8
9
  }
9
10
  export interface SynthesisOptions {
10
11
  pitch?: string | number;
11
12
  rate?: string | number;
12
13
  volume?: string | number;
14
+ inputType?: 'auto' | 'ssml' | 'text';
15
+ }
16
+ export interface WordBoundary {
17
+ type: "WordBoundary";
18
+ offset: number;
19
+ duration: number;
20
+ text: string;
13
21
  }
14
22
  export declare class EdgeTTS {
15
23
  private audio_stream;
16
24
  private audio_format;
25
+ private word_boundaries;
17
26
  private ws;
18
27
  getVoices(): Promise<Voice[]>;
19
28
  getVoicesByLanguage(locale: string): Promise<Voice[]>;
@@ -23,10 +32,14 @@ export declare class EdgeTTS {
23
32
  private validateRate;
24
33
  private validateVolume;
25
34
  synthesize(text: string, voice?: string, options?: SynthesisOptions): Promise<void>;
35
+ private detectSSML;
36
+ private escapeXML;
26
37
  private getSSML;
27
38
  private buildTTSConfigMessage;
28
39
  synthesizeStream(text: string, voice?: string, options?: SynthesisOptions): AsyncGenerator<Uint8Array, void, unknown>;
29
40
  private processAudioData;
41
+ private parseMetadata;
42
+ private generateSecMsGec;
30
43
  getDuration(): number;
31
44
  getAudioInfo(): {
32
45
  size: number;
@@ -37,4 +50,6 @@ export declare class EdgeTTS {
37
50
  toRaw(): string;
38
51
  toBase64(): string;
39
52
  toBuffer(): Buffer;
53
+ saveMetadata(outputPath: string): Promise<void>;
54
+ getWordBoundaries(): WordBoundary[];
40
55
  }
@@ -26,13 +26,19 @@ function ensureBuffer(data) {
26
26
  class EdgeTTS {
27
27
  audio_stream = [];
28
28
  audio_format = 'mp3';
29
+ word_boundaries = [];
29
30
  ws;
30
31
  async getVoices() {
31
- const response = await fetch(`${constants_1.Constants.VOICES_URL}?trustedclienttoken=${constants_1.Constants.TRUSTED_CLIENT_TOKEN}`);
32
+ const secMsGEC = await this.generateSecMsGec(constants_1.Constants.TRUSTED_CLIENT_TOKEN);
33
+ const response = await fetch(`${constants_1.Constants.VOICES_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}`, {
34
+ headers: {
35
+ "User-Agent": constants_1.Constants.USER_AGENT
36
+ },
37
+ });
32
38
  const data = await response.json();
33
39
  return data.map((voice) => {
34
- delete voice.VoiceTag;
35
- delete voice.SuggestedCodec;
40
+ voice.FriendlyName = voice.FriendlyName || voice.LocalName;
41
+ delete voice.SampleRateHertz;
36
42
  delete voice.Status;
37
43
  return voice;
38
44
  });
@@ -92,10 +98,16 @@ class EdgeTTS {
92
98
  return `${volumeValue}%`;
93
99
  }
94
100
  async synthesize(text, voice = 'en-US-AnaNeural', options = {}) {
101
+ const secMsGEC = await this.generateSecMsGec(constants_1.Constants.TRUSTED_CLIENT_TOKEN);
95
102
  return new Promise((resolve, reject) => {
96
103
  this.audio_stream = [];
97
- const req_id = this.generateUUID();
98
- this.ws = new ws_1.default(`${constants_1.Constants.WSS_URL}?trustedclienttoken=${constants_1.Constants.TRUSTED_CLIENT_TOKEN}&ConnectionId=${req_id}`);
104
+ const reqId = this.generateUUID();
105
+ 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}`;
106
+ this.ws = new ws_1.default(url, {
107
+ headers: {
108
+ "User-Agent": constants_1.Constants.USER_AGENT
109
+ }
110
+ });
99
111
  const SSML_text = this.getSSML(text, voice, options);
100
112
  const timeout = setTimeout(() => {
101
113
  if (this.ws && this.ws.readyState === ws_1.default.OPEN) {
@@ -106,7 +118,7 @@ class EdgeTTS {
106
118
  this.ws.on('open', () => {
107
119
  const message = this.buildTTSConfigMessage();
108
120
  this.ws.send(message);
109
- 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}`;
121
+ const speechMessage = `X-RequestId:${reqId}\r\nContent-Type:application/ssml+xml\r\nX-Timestamp:${new Date().toISOString()}Z\r\nPath:ssml\r\n\r\n${SSML_text}`;
110
122
  this.ws.send(speechMessage);
111
123
  });
112
124
  this.ws.on('message', (data) => {
@@ -125,14 +137,83 @@ class EdgeTTS {
125
137
  });
126
138
  });
127
139
  }
128
- getSSML(text, voice, options = {}) {
129
- if (typeof options.pitch === 'string') {
130
- options.pitch = options.pitch.replace('hz', 'Hz');
140
+ detectSSML(content) {
141
+ const trimmedContent = content.trim();
142
+ const looksLikeSSML = /^<\?xml|^<speak/i.test(trimmedContent);
143
+ if (!looksLikeSSML) {
144
+ return { isValid: true, isSSML: false };
145
+ }
146
+ const errors = [];
147
+ const hasSpeakTag = /<speak\b[^>]*>[\s\S]*<\/speak>/i.test(trimmedContent);
148
+ const hasVoiceTag = /<voice\b[^>]*>[\s\S]*<\/voice>/i.test(trimmedContent);
149
+ if (!hasSpeakTag) {
150
+ throw new Error('Invalid SSML: Missing <speak> tag');
151
+ }
152
+ if (!hasVoiceTag) {
153
+ throw new Error('Invalid SSML: Missing <voice> tag');
154
+ }
155
+ const hasCorrectNamespace = /xmlns="http:\/\/www\.w3\.org\/2001\/10\/synthesis"/i.test(trimmedContent);
156
+ if (!hasCorrectNamespace && hasSpeakTag) {
157
+ throw new Error('Invalid SSML: Missing or incorrect namespace declaration');
158
+ }
159
+ return {
160
+ isValid: errors.length === 0,
161
+ isSSML: hasSpeakTag || hasVoiceTag,
162
+ errors: errors.length > 0 ? errors : undefined
163
+ };
164
+ }
165
+ escapeXML(text) {
166
+ return text
167
+ .replace(/&/g, '&amp;')
168
+ .replace(/</g, '&lt;')
169
+ .replace(/>/g, '&gt;')
170
+ .replace(/"/g, '&quot;')
171
+ .replace(/'/g, '&apos;');
172
+ }
173
+ getSSML(content, voice, options = {}) {
174
+ const inputType = options.inputType || 'auto';
175
+ let treatAsSSML = false;
176
+ if (inputType === 'ssml') {
177
+ treatAsSSML = true;
178
+ }
179
+ else if (inputType === 'text') {
180
+ treatAsSSML = false;
181
+ }
182
+ else { // 'auto'
183
+ const detection = this.detectSSML(content);
184
+ treatAsSSML = detection.isSSML;
185
+ if (detection.isSSML) {
186
+ console.log('→ Detected SSML input');
187
+ if (!detection.isValid) {
188
+ console.warn('⚠ SSML validation warnings:', detection.errors);
189
+ }
190
+ }
191
+ else {
192
+ console.log('→ Detected plain text input');
193
+ }
194
+ }
195
+ if (treatAsSSML) {
196
+ let ssml = content.trim();
197
+ if (!ssml.includes('xmlns=')) {
198
+ ssml = ssml.replace(/<speak([^>]*)>/i, '<speak$1 xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="https://www.w3.org/2001/mstts">');
199
+ }
200
+ if (!/<voice\b[^>]*>/i.test(ssml) && voice) {
201
+ ssml = ssml.replace(/(<speak[^>]*>)([\s\S]*?)(<\/speak>)/i, `$1<voice name="${voice}">$2</voice>$3`);
202
+ }
203
+ return ssml;
131
204
  }
132
205
  const pitch = this.validatePitch(options.pitch ?? 0);
133
206
  const rate = this.validateRate(options.rate ?? 0);
134
207
  const volume = this.validateVolume(options.volume ?? 0);
135
- return `<speak version='1.0' xml:lang='en-US'><voice name='${voice}'><prosody pitch='${pitch}' rate='${rate}' volume='${volume}'>${text}</prosody></voice></speak>`;
208
+ const escapedText = this.escapeXML(content);
209
+ 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">
210
+ <voice name="${voice}">
211
+ <prosody pitch="${pitch}" rate="${rate}" volume="${volume}">
212
+ ${escapedText}
213
+ </prosody>
214
+ </voice>
215
+ </speak>
216
+ `;
136
217
  }
137
218
  buildTTSConfigMessage() {
138
219
  return `X-Timestamp:${new Date().toISOString()}Z\r\nContent-Type:application/json; charset=utf-8\r\nPath:speech.config\r\n\r\n` +
@@ -140,8 +221,14 @@ class EdgeTTS {
140
221
  }
141
222
  async *synthesizeStream(text, voice = 'en-US-AnaNeural', options = {}) {
142
223
  this.audio_stream = [];
143
- const req_id = this.generateUUID();
144
- this.ws = new ws_1.default(`${constants_1.Constants.WSS_URL}?trustedclienttoken=${constants_1.Constants.TRUSTED_CLIENT_TOKEN}&ConnectionId=${req_id}`);
224
+ const reqId = this.generateUUID();
225
+ const secMsGEC = await this.generateSecMsGec(constants_1.Constants.TRUSTED_CLIENT_TOKEN);
226
+ 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}`;
227
+ this.ws = new ws_1.default(url, {
228
+ headers: {
229
+ "User-Agent": constants_1.Constants.USER_AGENT
230
+ }
231
+ });
145
232
  const SSML_text = this.getSSML(text, voice, options);
146
233
  const queue = [];
147
234
  let done = false;
@@ -162,7 +249,7 @@ class EdgeTTS {
162
249
  this.ws.on('open', () => {
163
250
  const message = this.buildTTSConfigMessage();
164
251
  this.ws.send(message);
165
- 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}`;
252
+ const speechMessage = `X-RequestId:${reqId}\r\nContent-Type:application/ssml+xml\r\nX-Timestamp:${new Date().toISOString()}Z\r\nPath:ssml\r\n\r\n${SSML_text}`;
166
253
  this.ws.send(speechMessage);
167
254
  });
168
255
  this.ws.on('message', (data) => {
@@ -175,6 +262,15 @@ class EdgeTTS {
175
262
  this.audio_stream.push(chunk);
176
263
  push(chunk);
177
264
  }
265
+ if (buffer.toString().includes("Path:audio.metadata")) {
266
+ const metadataStart = buffer.indexOf("\r\n\r\n") + 4;
267
+ const metadataJson = buffer.toString().substring(metadataStart);
268
+ const meta = this.parseMetadata(metadataJson);
269
+ if (meta !== null) {
270
+ this.word_boundaries.push(meta);
271
+ }
272
+ return;
273
+ }
178
274
  if (buffer.toString().includes('Path:turn.end')) {
179
275
  this.ws?.close();
180
276
  }
@@ -217,10 +313,56 @@ class EdgeTTS {
217
313
  const audioChunk = buffer.subarray(audioStartIndex + needle.length);
218
314
  this.audio_stream.push(new Uint8Array(audioChunk));
219
315
  }
316
+ if (buffer.toString().includes("Path:audio.metadata")) {
317
+ const metadataStart = buffer.indexOf("\r\n\r\n") + 4;
318
+ const metadataJson = buffer.toString().substring(metadataStart);
319
+ const meta = this.parseMetadata(metadataJson);
320
+ if (meta !== null) {
321
+ this.word_boundaries.push(meta);
322
+ }
323
+ return;
324
+ }
220
325
  if (buffer.toString().includes("Path:turn.end")) {
221
326
  this.ws?.close();
222
327
  }
223
328
  }
329
+ parseMetadata(data, offsetCompensation = 0) {
330
+ let metadata;
331
+ try {
332
+ metadata = JSON.parse(data);
333
+ }
334
+ catch {
335
+ return null;
336
+ }
337
+ if (!metadata.Metadata) {
338
+ return null;
339
+ }
340
+ for (const metaObj of metadata.Metadata) {
341
+ if (metaObj.Type === "WordBoundary") {
342
+ const currentOffset = metaObj.Data.Offset + offsetCompensation;
343
+ const currentDuration = metaObj.Data.Duration;
344
+ return {
345
+ type: "WordBoundary",
346
+ offset: currentOffset,
347
+ duration: currentDuration,
348
+ text: metaObj.Data.text?.Text,
349
+ };
350
+ }
351
+ }
352
+ return null;
353
+ }
354
+ async generateSecMsGec(trustedClientToken) {
355
+ const ticks = Math.floor(Date.now() / 1000) + 11644473600;
356
+ const rounded = ticks - (ticks % 300);
357
+ const windowsTicks = rounded * 10000000;
358
+ const encoder = new TextEncoder();
359
+ const data = encoder.encode(`${windowsTicks}${trustedClientToken}`);
360
+ const hashBuffer = await crypto.subtle.digest('SHA-256', data);
361
+ return Array.from(new Uint8Array(hashBuffer))
362
+ .map(b => b.toString(16).padStart(2, '0'))
363
+ .join('')
364
+ .toUpperCase();
365
+ }
224
366
  getDuration() {
225
367
  if (this.audio_stream.length === 0) {
226
368
  throw new Error("No audio data available");
@@ -258,5 +400,15 @@ class EdgeTTS {
258
400
  }
259
401
  return buffer_1.Buffer.concat(this.audio_stream);
260
402
  }
403
+ async saveMetadata(outputPath) {
404
+ if (this.word_boundaries.length === 0) {
405
+ throw new Error("No metadata available to save.");
406
+ }
407
+ const json = JSON.stringify(this.word_boundaries, null, 4);
408
+ await (0, promises_1.writeFile)(outputPath, json);
409
+ }
410
+ getWordBoundaries() {
411
+ return this.word_boundaries;
412
+ }
261
413
  }
262
414
  exports.EdgeTTS = EdgeTTS;
package/package.json CHANGED
@@ -1,48 +1,48 @@
1
- {
2
- "name": "@andresaya/edge-tts",
3
- "version": "1.3.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
- "keywords": [
7
- "edge",
8
- "tts",
9
- "text-to-speech",
10
- "microsoft",
11
- "microsoft edge",
12
- "speech-synthesis"
13
- ],
14
- "author": "Andres Aya",
15
- "license": "GPL-3.0-only",
16
- "homepage": "https://github.com/andresayac/edge-tts",
17
- "bugs": {
18
- "url": "https://github.com/andresayac/edge-tts/issues"
19
- },
20
- "scripts": {
21
- "build": "tsc",
22
- "build:bin": "bun scripts/build.ts"
23
- },
24
- "devDependencies": {
25
- "@types/bun": "latest",
26
- "@types/ws": "^8.18.1",
27
- "typescript": "^5.6.2"
28
- },
29
- "dependencies": {
30
- "@types/uuid": "^10.0.0",
31
- "commander": "^12.1.0",
32
- "events": "^3.3.0",
33
- "fs": "^0.0.1-security",
34
- "ws": "^8.18.0"
35
- },
36
- "peerDependencies": {
37
- "typescript": "^5.0.0"
38
- },
39
- "files": [
40
- "dist"
41
- ],
42
- "bin": {
43
- "edge-tts": "./dist/cli/edge-tts.js"
44
- },
45
- "engines": {
46
- "bun": ">=0.5.0"
47
- }
48
- }
1
+ {
2
+ "name": "@andresaya/edge-tts",
3
+ "version": "1.4.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
+ "keywords": [
7
+ "edge",
8
+ "tts",
9
+ "text-to-speech",
10
+ "microsoft",
11
+ "microsoft edge",
12
+ "speech-synthesis"
13
+ ],
14
+ "author": "Andres Aya",
15
+ "license": "GPL-3.0-only",
16
+ "homepage": "https://github.com/andresayac/edge-tts",
17
+ "bugs": {
18
+ "url": "https://github.com/andresayac/edge-tts/issues"
19
+ },
20
+ "scripts": {
21
+ "build": "tsc",
22
+ "build:bin": "bun scripts/build.ts"
23
+ },
24
+ "devDependencies": {
25
+ "@types/bun": "latest",
26
+ "@types/ws": "^8.18.1",
27
+ "typescript": "^5.6.2"
28
+ },
29
+ "dependencies": {
30
+ "@types/uuid": "^10.0.0",
31
+ "commander": "^12.1.0",
32
+ "events": "^3.3.0",
33
+ "fs": "^0.0.1-security",
34
+ "ws": "^8.18.0"
35
+ },
36
+ "peerDependencies": {
37
+ "typescript": "^5.0.0"
38
+ },
39
+ "files": [
40
+ "dist"
41
+ ],
42
+ "bin": {
43
+ "edge-tts": "./dist/cli/edge-tts.js"
44
+ },
45
+ "engines": {
46
+ "bun": ">=0.5.0"
47
+ }
48
+ }