@bestcodes/edge-tts 2.0.0 → 3.0.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
@@ -21,9 +21,9 @@ bun add @bestcodes/edge-tts
21
21
  ### Get audio buffer
22
22
 
23
23
  ```ts
24
- import { streamSpeech } from "@bestcodes/edge-tts";
24
+ import { generateSpeech } from "@bestcodes/edge-tts";
25
25
 
26
- const audio = await streamSpeech({
26
+ const audio = await generateSpeech({
27
27
  text: "Hello, world!",
28
28
  voice: "en-US-EmmaMultilingualNeural",
29
29
  });
@@ -34,9 +34,9 @@ const audio = await streamSpeech({
34
34
  ### Save to file
35
35
 
36
36
  ```ts
37
- import { streamSpeechToFile } from "@bestcodes/edge-tts";
37
+ import { generateSpeechToFile } from "@bestcodes/edge-tts";
38
38
 
39
- await streamSpeechToFile({
39
+ await generateSpeechToFile({
40
40
  text: "Hello, world!",
41
41
  outputPath: "./output.mp3",
42
42
  });
@@ -45,14 +45,65 @@ await streamSpeechToFile({
45
45
  ### With subtitles
46
46
 
47
47
  ```ts
48
- import { streamSpeechWithSubtitles } from "@bestcodes/edge-tts";
48
+ import { generateSpeechWithSubtitlesToFile } from "@bestcodes/edge-tts";
49
49
 
50
- const { audio, subtitles } = await streamSpeechWithSubtitles({
50
+ const { audio, subtitles } = await generateSpeechWithSubtitlesToFile({
51
51
  text: "This text will have subtitles.",
52
52
  subtitlePath: "./subtitles.srt",
53
53
  });
54
54
  ```
55
55
 
56
+ ## Real-time Streaming
57
+
58
+ ### Stream audio chunks
59
+
60
+ ```ts
61
+ import { streamSpeech } from "@bestcodes/edge-tts";
62
+
63
+ for await (const chunk of streamSpeech({
64
+ text: "Hello, world!",
65
+ voice: "en-US-EmmaMultilingualNeural",
66
+ })) {
67
+ if (chunk.type === "audio" && chunk.data) {
68
+ // Process audio chunk in real-time
69
+ console.log(`Received ${chunk.data.length} bytes`);
70
+ }
71
+ }
72
+ ```
73
+
74
+ ### Stream directly to file with progress
75
+
76
+ ```ts
77
+ import { streamSpeechToFile } from "@bestcodes/edge-tts";
78
+
79
+ for await (const progress of streamSpeechToFile({
80
+ text: "Hello, world!",
81
+ outputPath: "./output.mp3",
82
+ })) {
83
+ console.log(
84
+ `Written: ${progress.bytesWritten} bytes (chunk: ${progress.chunkSize})`,
85
+ );
86
+ }
87
+ ```
88
+
89
+ ### Stream with subtitles (real-time updates)
90
+
91
+ ```ts
92
+ import { streamSpeechWithSubtitlesToFile } from "@bestcodes/edge-tts";
93
+
94
+ for await (const chunk of streamSpeechWithSubtitlesToFile({
95
+ text: "This text will have subtitles.",
96
+ subtitlePath: "./subtitles.srt",
97
+ })) {
98
+ if (chunk.type === "audio" && chunk.data) {
99
+ // Process audio chunk
100
+ } else if (chunk.subtitles) {
101
+ // Subtitles file updated in real-time
102
+ console.log("Subtitles updated:", chunk.subtitles);
103
+ }
104
+ }
105
+ ```
106
+
56
107
  ## Options
57
108
 
58
109
  ```ts
@@ -66,8 +117,8 @@ const { audio, subtitles } = await streamSpeechWithSubtitles({
66
117
  proxy?: string; // Optional proxy URL
67
118
  connectTimeoutSeconds?: number; // Default: 10
68
119
  receiveTimeoutSeconds?: number; // Default: 60
69
- outputPath?: string; // For streamSpeechToFile
70
- subtitlePath?: string; // For streamSpeechWithSubtitles
120
+ outputPath?: string; // For generateSpeechToFile/streamSpeechToFile
121
+ subtitlePath?: string; // For generateSpeechWithSubtitlesToFile/streamSpeechWithSubtitlesToFile
71
122
  }
72
123
  ```
73
124
 
@@ -87,9 +138,9 @@ const englishVoices = await findVoices({ Locale: "en-US" });
87
138
  ## Low-level API
88
139
 
89
140
  ```ts
90
- import { Raw } from "@bestcodes/edge-tts";
141
+ import { Experimental_Raw } from "@bestcodes/edge-tts";
91
142
 
92
- const communicate = new Raw.Communicate(
143
+ const communicate = new Experimental_Raw.Communicate(
93
144
  "Hello!",
94
145
  "en-US-EmmaMultilingualNeural",
95
146
  );
package/dist/index.d.ts CHANGED
@@ -94,7 +94,7 @@ declare class VoicesManager {
94
94
  static create(customVoices?: Voice[]): Promise<VoicesManager>;
95
95
  find(options: VoicesManagerFindOptions): VoicesManagerVoice[];
96
96
  }
97
- export declare const Raw: {
97
+ export declare const Experimental_Raw: {
98
98
  Communicate: typeof Communicate;
99
99
  SubMaker: typeof SubMaker;
100
100
  VoicesManager: typeof VoicesManager;
@@ -104,9 +104,9 @@ export declare const Raw: {
104
104
  utils: typeof utils;
105
105
  };
106
106
  /**
107
- * Options for streaming speech.
107
+ * Options for generating speech.
108
108
  */
109
- export interface StreamSpeechOptions {
109
+ export interface GenerateSpeechOptions {
110
110
  /** The text to convert to speech */
111
111
  text: string;
112
112
  /** Voice to use for synthesis (default: "en-US-EmmaMultilingualNeural") */
@@ -129,35 +129,35 @@ export interface StreamSpeechOptions {
129
129
  /**
130
130
  * Options for generating speech and writing to a file.
131
131
  */
132
- export interface GenerateSpeechOptions extends StreamSpeechOptions {
132
+ export interface GenerateSpeechToFileOptions extends GenerateSpeechOptions {
133
133
  /** Output file path for the generated audio */
134
134
  outputPath: string;
135
135
  }
136
136
  /**
137
- * Options for streaming speech with subtitles.
137
+ * Options for generating speech with subtitles.
138
138
  */
139
- export interface StreamSpeechWithSubtitlesOptions extends StreamSpeechOptions {
139
+ export interface GenerateSpeechWithSubtitlesOptions extends GenerateSpeechOptions {
140
140
  /** Output file path for the generated subtitles (SRT format) */
141
141
  subtitlePath: string;
142
142
  }
143
143
  /**
144
- * Stream speech audio from text.
144
+ * Generate speech audio from text.
145
145
  * @param options - Configuration options for speech synthesis
146
146
  * @returns Promise resolving to the audio buffer
147
147
  */
148
- export declare function streamSpeech(options: StreamSpeechOptions): Promise<Buffer>;
148
+ export declare function generateSpeech(options: GenerateSpeechOptions): Promise<Buffer>;
149
149
  /**
150
150
  * Generate speech from text and save it to a file.
151
151
  * @param options - Configuration options including the output file path
152
152
  * @returns Promise that resolves when the file is written
153
153
  */
154
- export declare function streamSpeechToFile(options: GenerateSpeechOptions): Promise<void>;
154
+ export declare function generateSpeechToFile(options: GenerateSpeechToFileOptions): Promise<void>;
155
155
  /**
156
- * Stream speech audio with generated subtitles.
156
+ * Generate speech audio with generated subtitles.
157
157
  * @param options - Configuration options including the subtitle file path
158
158
  * @returns Promise resolving to the audio buffer and subtitles string
159
159
  */
160
- export declare function streamSpeechWithSubtitles(options: StreamSpeechWithSubtitlesOptions): Promise<{
160
+ export declare function generateSpeechWithSubtitlesToFile(options: GenerateSpeechWithSubtitlesOptions): Promise<{
161
161
  audio: Buffer;
162
162
  subtitles: string;
163
163
  }>;
@@ -179,6 +179,29 @@ export declare function findVoices(options: {
179
179
  Language?: string;
180
180
  ShortName?: string;
181
181
  }, proxy?: string): Promise<VoicesManagerVoice[]>;
182
+ /**
183
+ * Stream speech audio in real-time from text.
184
+ * @param options - Configuration options for speech synthesis
185
+ * @returns AsyncGenerator yielding audio and metadata chunks as they arrive
186
+ */
187
+ export declare function streamSpeech(options: GenerateSpeechOptions): AsyncGenerator<TTSChunk>;
188
+ /**
189
+ * Stream speech audio with subtitles in real-time.
190
+ * @param options - Configuration options including the subtitle file path
191
+ * @returns AsyncGenerator yielding audio chunks and subtitle data as they arrive
192
+ */
193
+ export declare function streamSpeechWithSubtitlesToFile(options: GenerateSpeechWithSubtitlesOptions): AsyncGenerator<TTSChunk & {
194
+ subtitles?: string;
195
+ }>;
196
+ /**
197
+ * Stream speech audio directly to a file in real-time.
198
+ * @param options - Configuration options including the output file path
199
+ * @returns AsyncGenerator yielding progress information as chunks are written
200
+ */
201
+ export declare function streamSpeechToFile(options: GenerateSpeechToFileOptions): AsyncGenerator<{
202
+ bytesWritten: number;
203
+ chunkSize: number;
204
+ }>;
182
205
 
183
206
  declare namespace utils {
184
207
  export { connectId, dateToString, escapeXml, getHeadersAndData, removeIncompatibleCharacters, splitTextByByteLength, unescapeXml };
package/dist/index.mjs CHANGED
@@ -1,17 +1,17 @@
1
- var t=Object.defineProperty;var a=(E,S)=>{for(var $ in S)t(E,$,{get:S[$],enumerable:!0,configurable:!0,set:(r)=>S[$]=()=>r})};import*as J from"fs";import*as l from"path";import{HttpsProxyAgent as U0}from"https-proxy-agent";import _0 from"ws";var v="6A5AA1D4EAFF4E9FB37E23D68491D6F4",m="wss://speech.platform.bing.com/consumer/speech/synthesize/readaloud/edge/v1?TrustedClientToken=6A5AA1D4EAFF4E9FB37E23D68491D6F4",g="https://speech.platform.bing.com/consumer/speech/synthesize/readaloud/voices/list?trustedclienttoken=6A5AA1D4EAFF4E9FB37E23D68491D6F4",u="en-US-EmmaMultilingualNeural";var L="130.0.2849.68".split(".")[0],Z="1-130.0.2849.68",p={"User-Agent":`Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${L}.0.0.0 Safari/537.36 Edg/${L}.0.0.0`,"Accept-Encoding":"gzip, deflate, br, zstd","Accept-Language":"en-US,en;q=0.9"},i={...p,Pragma:"no-cache","Cache-Control":"no-cache",Origin:"chrome-extension://jdiccldimpdaibmpdkjnbmckianbfold","Sec-WebSocket-Version":"13"},d={...p,Authority:"speech.platform.bing.com","Sec-CH-UA":`" Not;A Brand";v="99", "Microsoft Edge";v="${L}", "Chromium";v="${L}"`,"Sec-CH-UA-Mobile":"?0",Accept:"*/*","Sec-Fetch-Site":"none","Sec-Fetch-Mode":"cors","Sec-Fetch-Dest":"empty"};import*as j from"crypto";class M extends Error{constructor(E){super(E);this.name="EdgeTTSException"}}class f extends M{constructor(E){super(E);this.name="UnknownResponse"}}class I extends M{constructor(E){super(E);this.name="UnexpectedResponse"}}class n extends M{constructor(E){super(E);this.name="NoAudioReceived"}}class Q extends M{constructor(E){super(E);this.name="SkewAdjustmentError"}}var e=11644473600;class N{static clockSkewSeconds=0;static adjustClockSkewSeconds(E){N.clockSkewSeconds+=E}static getUnixTimestamp(){return Date.now()/1000+N.clockSkewSeconds}static parseRFC2616Date(E){let S=Date.parse(E);if(isNaN(S))return null;return S/1000}static handleClientResponseError(E){let S=E.date||E.Date;if(!S||typeof S!=="string")throw new Q("No server date in headers.");let $=N.parseRFC2616Date(S);if($===null)throw new Q(`Failed to parse server date: ${S}`);let r=N.getUnixTimestamp();N.adjustClockSkewSeconds($-r)}static generateSecMsGec(){let E=N.getUnixTimestamp();E+=e,E-=E%300,E*=1e7;let S=`${E.toFixed(0)}${v}`;return j.createHash("sha256").update(S,"ascii").digest("hex").toUpperCase()}static generateMuid(){return j.randomBytes(16).toString("hex").toUpperCase()}static headersWithMuid(E){return{...E,Cookie:`muid=${N.generateMuid()};`}}}class R{voice;rate;volume;pitch;boundary;constructor(E,S="+0%",$="+0%",r="+0Hz",U="SentenceBoundary"){this.voice=E,this.rate=S,this.volume=$,this.pitch=r,this.boundary=U,this.validate()}validateStringParam(E,S,$){if(!$.test(S))throw Error(`Invalid ${E} '${S}'.`)}validate(){let E=this.voice.match(/^([a-z]{2,})-([A-Z]{2,})-(.+Neural)$/);if(E){let S=E[1],$=E[2],r=E[3];if(r&&r.includes("-"))$=`${$}-${r.split("-")[0]}`,r=r.split("-")[1];this.voice=`Microsoft Server Speech Text to Speech Voice (${S}-${$}, ${r})`}this.validateStringParam("voice",this.voice,/^Microsoft Server Speech Text to Speech Voice \(.+,.+\)$/),this.validateStringParam("rate",this.rate,/^[+-]\d+%$/),this.validateStringParam("volume",this.volume,/^[+-]\d+%$/),this.validateStringParam("pitch",this.pitch,/^[+-]\d+Hz$/)}toSSML(E){return`<speak version='1.0' xmlns='http://www.w3.org/2001/10/synthesis' xml:lang='en-US'><voice name='${this.voice}'><prosody pitch='${this.pitch}' rate='${this.rate}' volume='${this.volume}'>${E}</prosody></voice></speak>`}}var b={};a(b,{unescapeXml:()=>y,splitTextByByteLength:()=>h,removeIncompatibleCharacters:()=>c,getHeadersAndData:()=>k,escapeXml:()=>x,dateToString:()=>P,connectId:()=>D});import{v4 as E0}from"uuid";function D(){return E0().replace(/-/g,"")}function x(E){return E.replace(/[<>&'"]/g,(S)=>{switch(S){case"<":return"&lt;";case">":return"&gt;";case"&":return"&amp;";case"'":return"&apos;";case'"':return"&quot;";default:return S}})}function y(E){return E.replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&amp;/g,"&").replace(/&apos;/g,"'").replace(/&quot;/g,'"')}function P(){return new Date().toUTCString()}function c(E){return(Buffer.isBuffer(E)?E.toString("utf-8"):E).split("").map(($)=>{let r=$.charCodeAt(0);if(r>=0&&r<=8||r>=11&&r<=12||r>=14&&r<=31)return" ";return $}).join("")}function S0(E,S){let $=E.subarray(0,S),r=$.lastIndexOf(10);if(r<0)r=$.lastIndexOf(32);return r}function $0(E){let S=E.length;while(S>0){let $=E.subarray(0,S);try{let r=$[$.length-1];if(r!==void 0&&(r&128)===0)return S;let U=$.toString("utf-8");if(Buffer.from(U).length===$.length&&!U.endsWith("�"))return S;S--}catch(r){S--}}return S}function r0(E,S){let $=E.subarray(0,S),r=$.lastIndexOf(38);if(r>-1){if($.indexOf(59,r)===-1)return r}return S}function*h(E,S){let $=Buffer.isBuffer(E)?E:Buffer.from(E,"utf-8");if(S<=0)throw Error("byteLength must be > 0");while($.length>S){let U=S0($,S);if(U<0)U=$0($.subarray(0,S));if(U=r0($,U),U<=0)throw Error("Maximum byte length too small for text structure");let F=$.subarray(0,U).toString("utf-8").trim();if(F.length>0)yield Buffer.from(F);$=$.subarray(U+(U>0?0:1))}let r=$.toString("utf-8").trim();if(r.length>0)yield Buffer.from(r)}function k(E,S){let $={},r=E.subarray(0,S),U=E.subarray(S+2),_=r.toString("utf-8").split(`\r
2
- `);for(let F of _){let[O,H]=F.split(":",2);if(O&&H)$[O.trim()]=H.trim()}return{headers:$,data:U}}class X{ttsConfig;texts;options;state;constructor(E,S=u,$={}){this.options={rate:"+0%",volume:"+0%",pitch:"+0Hz",boundary:"SentenceBoundary",connectTimeoutSeconds:10,receiveTimeoutSeconds:60,...$},this.ttsConfig=new R(S,this.options.rate,this.options.volume,this.options.pitch,this.options.boundary);let r=c(E),U=x(r);this.texts=Array.from(h(U,4096)),this.state={partialText:Buffer.alloc(0),offsetCompensation:0,lastDurationOffset:0,streamWasCalled:!1}}parseMetadata(E){let S=E.toString("utf-8"),$=JSON.parse(S);for(let r of $.Metadata){let U=r.Type;if(U==="WordBoundary"||U==="SentenceBoundary"){let _=r.Data.Offset+this.state.offsetCompensation,F=r.Data.Duration;return{type:U,offset:_,duration:F,text:y(r.Data.text.Text)}}if(U==="SessionEnd")continue;throw new f(`Unknown metadata type: ${U}`)}throw new I("No boundary metadata found")}async*streamInternal(E){let S=this,$=!1,r=()=>{let W=S.ttsConfig.boundary==="WordBoundary",w=W?"true":"false",C=!W?"true":"false",K=`X-Timestamp:${P()}\r
1
+ var a=Object.defineProperty;var t=(E,$)=>{for(var U in $)a(E,U,{get:$[U],enumerable:!0,configurable:!0,set:(_)=>$[U]=()=>_})};import*as w from"fs";import*as A from"path";import{HttpsProxyAgent as S0}from"https-proxy-agent";import F0 from"ws";var m="6A5AA1D4EAFF4E9FB37E23D68491D6F4",n="wss://speech.platform.bing.com/consumer/speech/synthesize/readaloud/edge/v1?TrustedClientToken=6A5AA1D4EAFF4E9FB37E23D68491D6F4",k="https://speech.platform.bing.com/consumer/speech/synthesize/readaloud/voices/list?trustedclienttoken=6A5AA1D4EAFF4E9FB37E23D68491D6F4",p="en-US-EmmaMultilingualNeural";var Q="130.0.2849.68".split(".")[0],R="1-130.0.2849.68",u={"User-Agent":`Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${Q}.0.0.0 Safari/537.36 Edg/${Q}.0.0.0`,"Accept-Encoding":"gzip, deflate, br, zstd","Accept-Language":"en-US,en;q=0.9"},s={...u,Pragma:"no-cache","Cache-Control":"no-cache",Origin:"chrome-extension://jdiccldimpdaibmpdkjnbmckianbfold","Sec-WebSocket-Version":"13"},d={...u,Authority:"speech.platform.bing.com","Sec-CH-UA":`" Not;A Brand";v="99", "Microsoft Edge";v="${Q}", "Chromium";v="${Q}"`,"Sec-CH-UA-Mobile":"?0",Accept:"*/*","Sec-Fetch-Site":"none","Sec-Fetch-Mode":"cors","Sec-Fetch-Dest":"empty"};import*as P from"crypto";class q extends Error{constructor(E){super(E);this.name="EdgeTTSException"}}class x extends q{constructor(E){super(E);this.name="UnknownResponse"}}class Z extends q{constructor(E){super(E);this.name="UnexpectedResponse"}}class y extends q{constructor(E){super(E);this.name="NoAudioReceived"}}class D extends q{constructor(E){super(E);this.name="SkewAdjustmentError"}}var e=11644473600;class W{static clockSkewSeconds=0;static adjustClockSkewSeconds(E){W.clockSkewSeconds+=E}static getUnixTimestamp(){return Date.now()/1000+W.clockSkewSeconds}static parseRFC2616Date(E){let $=Date.parse(E);if(isNaN($))return null;return $/1000}static handleClientResponseError(E){let $=E.date||E.Date;if(!$||typeof $!=="string")throw new D("No server date in headers.");let U=W.parseRFC2616Date($);if(U===null)throw new D(`Failed to parse server date: ${$}`);let _=W.getUnixTimestamp();W.adjustClockSkewSeconds(U-_)}static generateSecMsGec(){let E=W.getUnixTimestamp();E+=e,E-=E%300,E*=1e7;let $=`${E.toFixed(0)}${m}`;return P.createHash("sha256").update($,"ascii").digest("hex").toUpperCase()}static generateMuid(){return P.randomBytes(16).toString("hex").toUpperCase()}static headersWithMuid(E){return{...E,Cookie:`muid=${W.generateMuid()};`}}}class L{voice;rate;volume;pitch;boundary;constructor(E,$="+0%",U="+0%",_="+0Hz",S="SentenceBoundary"){this.voice=E,this.rate=$,this.volume=U,this.pitch=_,this.boundary=S,this.validate()}validateStringParam(E,$,U){if(!U.test($))throw Error(`Invalid ${E} '${$}'.`)}validate(){let E=this.voice.match(/^([a-z]{2,})-([A-Z]{2,})-(.+Neural)$/);if(E){let $=E[1],U=E[2],_=E[3];if(_&&_.includes("-"))U=`${U}-${_.split("-")[0]}`,_=_.split("-")[1];this.voice=`Microsoft Server Speech Text to Speech Voice (${$}-${U}, ${_})`}this.validateStringParam("voice",this.voice,/^Microsoft Server Speech Text to Speech Voice \(.+,.+\)$/),this.validateStringParam("rate",this.rate,/^[+-]\d+%$/),this.validateStringParam("volume",this.volume,/^[+-]\d+%$/),this.validateStringParam("pitch",this.pitch,/^[+-]\d+Hz$/)}toSSML(E){return`<speak version='1.0' xmlns='http://www.w3.org/2001/10/synthesis' xml:lang='en-US'><voice name='${this.voice}'><prosody pitch='${this.pitch}' rate='${this.rate}' volume='${this.volume}'>${E}</prosody></voice></speak>`}}var c={};t(c,{unescapeXml:()=>v,splitTextByByteLength:()=>h,removeIncompatibleCharacters:()=>l,getHeadersAndData:()=>f,escapeXml:()=>b,dateToString:()=>g,connectId:()=>V});import{v4 as E0}from"uuid";function V(){return E0().replace(/-/g,"")}function b(E){return E.replace(/[<>&'"]/g,($)=>{switch($){case"<":return"&lt;";case">":return"&gt;";case"&":return"&amp;";case"'":return"&apos;";case'"':return"&quot;";default:return $}})}function v(E){return E.replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&amp;/g,"&").replace(/&apos;/g,"'").replace(/&quot;/g,'"')}function g(){return new Date().toUTCString()}function l(E){return(Buffer.isBuffer(E)?E.toString("utf-8"):E).split("").map((U)=>{let _=U.charCodeAt(0);if(_>=0&&_<=8||_>=11&&_<=12||_>=14&&_<=31)return" ";return U}).join("")}function $0(E,$){let U=E.subarray(0,$),_=U.lastIndexOf(10);if(_<0)_=U.lastIndexOf(32);return _}function U0(E){let $=E.length;while($>0){let U=E.subarray(0,$);try{let _=U[U.length-1];if(_!==void 0&&(_&128)===0)return $;let S=U.toString("utf-8");if(Buffer.from(S).length===U.length&&!S.endsWith("�"))return $;$--}catch(_){$--}}return $}function _0(E,$){let U=E.subarray(0,$),_=U.lastIndexOf(38);if(_>-1){if(U.indexOf(59,_)===-1)return _}return $}function*h(E,$){let U=Buffer.isBuffer(E)?E:Buffer.from(E,"utf-8");if($<=0)throw Error("byteLength must be > 0");while(U.length>$){let S=$0(U,$);if(S<0)S=U0(U.subarray(0,$));if(S=_0(U,S),S<=0)throw Error("Maximum byte length too small for text structure");let F=U.subarray(0,S).toString("utf-8").trim();if(F.length>0)yield Buffer.from(F);U=U.subarray(S+(S>0?0:1))}let _=U.toString("utf-8").trim();if(_.length>0)yield Buffer.from(_)}function f(E,$){let U={},_=E.subarray(0,$),S=E.subarray($+2),N=_.toString("utf-8").split(`\r
2
+ `);for(let F of N){let[O,r]=F.split(":",2);if(O&&r)U[O.trim()]=r.trim()}return{headers:U,data:S}}class G{ttsConfig;texts;options;state;constructor(E,$=p,U={}){this.options={rate:"+0%",volume:"+0%",pitch:"+0Hz",boundary:"SentenceBoundary",connectTimeoutSeconds:10,receiveTimeoutSeconds:60,...U},this.ttsConfig=new L($,this.options.rate,this.options.volume,this.options.pitch,this.options.boundary);let _=l(E),S=b(_);this.texts=Array.from(h(S,4096)),this.state={partialText:Buffer.alloc(0),offsetCompensation:0,lastDurationOffset:0,streamWasCalled:!1}}parseMetadata(E){let $=E.toString("utf-8"),U=JSON.parse($);for(let _ of U.Metadata){let S=_.Type;if(S==="WordBoundary"||S==="SentenceBoundary"){let N=_.Data.Offset+this.state.offsetCompensation,F=_.Data.Duration;return{type:S,offset:N,duration:F,text:v(_.Data.text.Text)}}if(S==="SessionEnd")continue;throw new x(`Unknown metadata type: ${S}`)}throw new Z("No boundary metadata found")}async*streamInternal(E){let $=this,U=!1,_=()=>{let C=$.ttsConfig.boundary==="WordBoundary",z=C?"true":"false",K=!C?"true":"false",H=`X-Timestamp:${g()}\r
3
3
  Content-Type:application/json; charset=utf-8\r
4
4
  Path:speech.config\r
5
5
  \r
6
- {"context":{"synthesis":{"audio":{"metadataoptions":{"sentenceBoundaryEnabled":"${C}","wordBoundaryEnabled":"${w}"},"outputFormat":"audio-24khz-48kbitrate-mono-mp3"}}}}`;E.send(K)},U=()=>{let W=D(),w=P(),C=S.ttsConfig.toSSML(S.state.partialText.toString("utf-8")),K=`X-RequestId:${W}\r
6
+ {"context":{"synthesis":{"audio":{"metadataoptions":{"sentenceBoundaryEnabled":"${K}","wordBoundaryEnabled":"${z}"},"outputFormat":"audio-24khz-48kbitrate-mono-mp3"}}}}`;E.send(H)},S=()=>{let C=V(),z=g(),K=$.ttsConfig.toSSML($.state.partialText.toString("utf-8")),H=`X-RequestId:${C}\r
7
7
  Content-Type:application/ssml+xml\r
8
- X-Timestamp:${w}Z\r
8
+ X-Timestamp:${z}Z\r
9
9
  Path:ssml\r
10
10
  \r
11
- `+C;E.send(K)};r(),U();let _=[],F=null,O=null,H=!1;E.on("message",(W,w)=>{if(H)return;if(_.push({data:W,isBinary:w}),F)F(),F=null}),E.on("error",(W)=>{if(H)return;if(O)O(W);H=!0}),E.on("close",()=>{if(H=!0,F)F()});while(!H||_.length>0){if(_.length===0){if(await new Promise((C,K)=>{F=C,O=K}),H&&_.length===0)break}let{data:W,isBinary:w}=_.shift();if(!w){let C=Buffer.from(W),K=C.indexOf(`\r
11
+ `+K;E.send(H)};_(),S();let N=[],F=null,O=null,r=!1;E.on("message",(C,z)=>{if(r)return;if(N.push({data:C,isBinary:z}),F)F(),F=null}),E.on("error",(C)=>{if(r)return;if(O)O(C);r=!0}),E.on("close",()=>{if(r=!0,F)F()});while(!r||N.length>0){if(N.length===0){if(await new Promise((K,H)=>{F=K,O=H}),r&&N.length===0)break}let{data:C,isBinary:z}=N.shift();if(!z){let K=Buffer.from(C),H=K.indexOf(`\r
12
12
  \r
13
- `),{headers:B,data:T}=k(C,K),z=B.Path;if(z==="audio.metadata"){let G=S.parseMetadata(T);yield G,S.state.lastDurationOffset=(G.offset||0)+(G.duration||0)}else if(z==="turn.end"){S.state.offsetCompensation=S.state.lastDurationOffset+8750000;break}}else{let C=W;if(C.length<2)throw new I("Binary message too short for header length");let K=C.readUInt16BE(0);if(K>C.length)throw new I("Header length greater than data length");let{headers:B,data:T}=k(C,K);if(B.Path!=="audio")throw new I("Binary message path is not audio");let z=B["Content-Type"];if(!z&&T.length===0)continue;if(z!=="audio/mpeg"&&z!==void 0)throw new I(`Unexpected Content-Type: ${z}`);if(!z&&T.length>0)throw new I("No Content-Type but got data");if(T.length===0)throw new I("Audio data is empty");$=!0,yield{type:"audio",data:T}}}if(!$)throw new n("No audio received from service.")}async*stream(){if(this.state.streamWasCalled)throw Error("stream() can only be called once.");this.state.streamWasCalled=!0;let E=this.options.proxy?new U0(this.options.proxy):void 0,S=`${m}&ConnectionId=${D()}&Sec-MS-GEC=${N.generateSecMsGec()}&Sec-MS-GEC-Version=${Z}`;for(let $ of this.texts){this.state.partialText=$;let r=0;while(!0)try{let U=new _0(S,{headers:N.headersWithMuid(i),agent:E,perMessageDeflate:!1});await new Promise((F,O)=>{U.once("open",F),U.once("error",O)});let _=this.streamInternal(U);for await(let F of _)yield F;U.close();break}catch(U){if(U.name==="UnexpectedServerResponse"&&U.code===403&&r===0)throw U;throw U}}}}function o(E){let S=new Date(E),$=Math.floor(E/3600000),r=S.getUTCMinutes(),U=S.getUTCSeconds(),_=S.getUTCMilliseconds();return`${$.toString().padStart(2,"0")}:${r.toString().padStart(2,"0")}:${U.toString().padStart(2,"0")},${_.toString().padStart(3,"0")}`}class V{cues=[];type=null;feed(E){if(E.type!=="WordBoundary"&&E.type!=="SentenceBoundary")throw Error("Invalid message type, expected 'WordBoundary' or 'SentenceBoundary'.");if(this.type===null)this.type=E.type;else if(this.type!==E.type)throw Error(`Expected message type '${this.type}', but got '${E.type}'.`);if(E.offset===void 0||E.duration===void 0||E.text===void 0)return;let S=E.offset/1e4,$=E.duration/1e4;this.cues.push({index:this.cues.length+1,start:S,end:S+$,content:E.text})}getSrt(){return this.cues.map((E)=>{return`${E.index}
14
- ${o(E.start)} --> ${o(E.end)}
13
+ `),{headers:Y,data:I}=f(K,H),J=Y.Path;if(J==="audio.metadata"){let T=$.parseMetadata(I);yield T,$.state.lastDurationOffset=(T.offset||0)+(T.duration||0)}else if(J==="turn.end"){$.state.offsetCompensation=$.state.lastDurationOffset+8750000;break}}else{let K=C;if(K.length<2)throw new Z("Binary message too short for header length");let H=K.readUInt16BE(0);if(H>K.length)throw new Z("Header length greater than data length");let{headers:Y,data:I}=f(K,H);if(Y.Path!=="audio")throw new Z("Binary message path is not audio");let J=Y["Content-Type"];if(!J&&I.length===0)continue;if(J!=="audio/mpeg"&&J!==void 0)throw new Z(`Unexpected Content-Type: ${J}`);if(!J&&I.length>0)throw new Z("No Content-Type but got data");if(I.length===0)throw new Z("Audio data is empty");U=!0,yield{type:"audio",data:I}}}if(!U)throw new y("No audio received from service.")}async*stream(){if(this.state.streamWasCalled)throw Error("stream() can only be called once.");this.state.streamWasCalled=!0;let E=this.options.proxy?new S0(this.options.proxy):void 0,$=`${n}&ConnectionId=${V()}&Sec-MS-GEC=${W.generateSecMsGec()}&Sec-MS-GEC-Version=${R}`;for(let U of this.texts){this.state.partialText=U;let _=0;while(!0)try{let S=new F0($,{headers:W.headersWithMuid(s),agent:E,perMessageDeflate:!1});await new Promise((F,O)=>{S.once("open",F),S.once("error",O)});let N=this.streamInternal(S);for await(let F of N)yield F;S.close();break}catch(S){if(S.name==="UnexpectedServerResponse"&&S.code===403&&_===0)throw S;throw S}}}}function i(E){let $=new Date(E),U=Math.floor(E/3600000),_=$.getUTCMinutes(),S=$.getUTCSeconds(),N=$.getUTCMilliseconds();return`${U.toString().padStart(2,"0")}:${_.toString().padStart(2,"0")}:${S.toString().padStart(2,"0")},${N.toString().padStart(3,"0")}`}class X{cues=[];type=null;feed(E){if(E.type!=="WordBoundary"&&E.type!=="SentenceBoundary")throw Error("Invalid message type, expected 'WordBoundary' or 'SentenceBoundary'.");if(this.type===null)this.type=E.type;else if(this.type!==E.type)throw Error(`Expected message type '${this.type}', but got '${E.type}'.`);if(E.offset===void 0||E.duration===void 0||E.text===void 0)return;let $=E.offset/1e4,U=E.duration/1e4;this.cues.push({index:this.cues.length+1,start:$,end:$+U,content:E.text})}getSrt(){return this.cues.map((E)=>{return`${E.index}
14
+ ${i(E.start)} --> ${i(E.end)}
15
15
  ${E.content}
16
16
 
17
- `}).join("")}}import s from"axios";import{HttpsProxyAgent as F0}from"https-proxy-agent";async function N0(E){let S=`${g}&Sec-MS-GEC=${N.generateSecMsGec()}&Sec-MS-GEC-Version=${Z}`,$=N.headersWithMuid(d),r=E?new F0(E):void 0,U=await s.get(S,{headers:$,httpsAgent:r,proxy:!1,validateStatus:(_)=>_<500});if(U.status===403){N.handleClientResponseError(U.headers);let _=`${g}&Sec-MS-GEC=${N.generateSecMsGec()}&Sec-MS-GEC-Version=${Z}`;return(await s.get(_,{headers:N.headersWithMuid(d),httpsAgent:r,proxy:!1})).data}if(U.status>=400)throw Error(`Failed to list voices: ${U.status} ${U.statusText}`);return U.data}async function q(E){let S=await N0(E);return S.forEach(($)=>{if(!$.VoiceTag)$.VoiceTag={ContentCategories:[],VoicePersonalities:[]};if(!$.VoiceTag.ContentCategories)$.VoiceTag.ContentCategories=[];if(!$.VoiceTag.VoicePersonalities)$.VoiceTag.VoicePersonalities=[]}),S}class A{voices=[];createCalled=!1;static async create(E){let S=new A,$=E||await q();return S.voices=$.map((r)=>({...r,Language:r.Locale.split("-")[0]??""})),S.createCalled=!0,S}find(E){if(!this.createCalled)throw Error("VoicesManager.find() called before VoicesManager.create()");return this.voices.filter((S)=>{let $=!0;if(E.Gender&&S.Gender!==E.Gender)$=!1;if(E.Locale&&S.Locale!==E.Locale)$=!1;if(E.Language&&S.Language!==E.Language)$=!1;if(E.ShortName&&S.ShortName!==E.ShortName)$=!1;return $})}}var f0={Communicate:X,SubMaker:V,VoicesManager:A,listVoices:q,DRM:N,TTSConfig:R,utils:b};async function C0(E){let{text:S,voice:$="en-US-EmmaMultilingualNeural",rate:r="+0%",volume:U="+0%",pitch:_="+0Hz",boundary:F="SentenceBoundary",proxy:O,connectTimeoutSeconds:H,receiveTimeoutSeconds:W}=E,w=new X(S,$,{rate:r,volume:U,pitch:_,boundary:F,proxy:O,connectTimeoutSeconds:H,receiveTimeoutSeconds:W}),C=[];for await(let K of w.stream())if(K.type==="audio"&&K.data)C.push(K.data);return Buffer.concat(C)}async function n0(E){let{outputPath:S,...$}=E,r=await C0($),U=l.dirname(S);if(!J.existsSync(U))J.mkdirSync(U,{recursive:!0});J.writeFileSync(S,r)}async function x0(E){let{text:S,voice:$="en-US-EmmaMultilingualNeural",rate:r="+0%",volume:U="+0%",pitch:_="+0Hz",boundary:F="WordBoundary",proxy:O,connectTimeoutSeconds:H,receiveTimeoutSeconds:W,subtitlePath:w}=E,C=new X(S,$,{rate:r,volume:U,pitch:_,boundary:F,proxy:O,connectTimeoutSeconds:H,receiveTimeoutSeconds:W}),K=new V,B=[];for await(let Y of C.stream())if(Y.type==="audio"&&Y.data)B.push(Y.data);else if(Y.type==="WordBoundary"||Y.type==="SentenceBoundary")K.feed(Y);let T=Buffer.concat(B),z=K.getSrt(),G=l.dirname(w);if(!J.existsSync(G))J.mkdirSync(G,{recursive:!0});return J.writeFileSync(w,z),{audio:T,subtitles:z}}async function y0(E){return q(E)}async function c0(E,S){return(await A.create(S?await q(S):void 0)).find(E)}export{x0 as streamSpeechWithSubtitles,n0 as streamSpeechToFile,C0 as streamSpeech,y0 as getVoices,c0 as findVoices,f0 as Raw};
17
+ `}).join("")}}import o from"axios";import{HttpsProxyAgent as N0}from"https-proxy-agent";async function K0(E){let $=`${k}&Sec-MS-GEC=${W.generateSecMsGec()}&Sec-MS-GEC-Version=${R}`,U=W.headersWithMuid(d),_=E?new N0(E):void 0,S=await o.get($,{headers:U,httpsAgent:_,proxy:!1,validateStatus:(N)=>N<500});if(S.status===403){W.handleClientResponseError(S.headers);let N=`${k}&Sec-MS-GEC=${W.generateSecMsGec()}&Sec-MS-GEC-Version=${R}`;return(await o.get(N,{headers:W.headersWithMuid(d),httpsAgent:_,proxy:!1})).data}if(S.status>=400)throw Error(`Failed to list voices: ${S.status} ${S.statusText}`);return S.data}async function M(E){let $=await K0(E);return $.forEach((U)=>{if(!U.VoiceTag)U.VoiceTag={ContentCategories:[],VoicePersonalities:[]};if(!U.VoiceTag.ContentCategories)U.VoiceTag.ContentCategories=[];if(!U.VoiceTag.VoicePersonalities)U.VoiceTag.VoicePersonalities=[]}),$}class j{voices=[];createCalled=!1;static async create(E){let $=new j,U=E||await M();return $.voices=U.map((_)=>({..._,Language:_.Locale.split("-")[0]??""})),$.createCalled=!0,$}find(E){if(!this.createCalled)throw Error("VoicesManager.find() called before VoicesManager.create()");return this.voices.filter(($)=>{let U=!0;if(E.Gender&&$.Gender!==E.Gender)U=!1;if(E.Locale&&$.Locale!==E.Locale)U=!1;if(E.Language&&$.Language!==E.Language)U=!1;if(E.ShortName&&$.ShortName!==E.ShortName)U=!1;return U})}}var x0={Communicate:G,SubMaker:X,VoicesManager:j,listVoices:M,DRM:W,TTSConfig:L,utils:c};async function H0(E){let{text:$,voice:U="en-US-EmmaMultilingualNeural",rate:_="+0%",volume:S="+0%",pitch:N="+0Hz",boundary:F="SentenceBoundary",proxy:O,connectTimeoutSeconds:r,receiveTimeoutSeconds:C}=E,z=new G($,U,{rate:_,volume:S,pitch:N,boundary:F,proxy:O,connectTimeoutSeconds:r,receiveTimeoutSeconds:C}),K=[];for await(let H of z.stream())if(H.type==="audio"&&H.data)K.push(H.data);return Buffer.concat(K)}async function y0(E){let{outputPath:$,...U}=E,_=await H0(U),S=A.dirname($);if(!w.existsSync(S))w.mkdirSync(S,{recursive:!0});w.writeFileSync($,_)}async function b0(E){let{text:$,voice:U="en-US-EmmaMultilingualNeural",rate:_="+0%",volume:S="+0%",pitch:N="+0Hz",boundary:F="WordBoundary",proxy:O,connectTimeoutSeconds:r,receiveTimeoutSeconds:C,subtitlePath:z}=E,K=new G($,U,{rate:_,volume:S,pitch:N,boundary:F,proxy:O,connectTimeoutSeconds:r,receiveTimeoutSeconds:C}),H=new X,Y=[];for await(let B of K.stream())if(B.type==="audio"&&B.data)Y.push(B.data);else if(B.type==="WordBoundary"||B.type==="SentenceBoundary")H.feed(B);let I=Buffer.concat(Y),J=H.getSrt(),T=A.dirname(z);if(!w.existsSync(T))w.mkdirSync(T,{recursive:!0});return w.writeFileSync(z,J),{audio:I,subtitles:J}}async function v0(E){return M(E)}async function l0(E,$){return(await j.create($?await M($):void 0)).find(E)}async function*C0(E){let{text:$,voice:U="en-US-EmmaMultilingualNeural",rate:_="+0%",volume:S="+0%",pitch:N="+0Hz",boundary:F="SentenceBoundary",proxy:O,connectTimeoutSeconds:r,receiveTimeoutSeconds:C}=E,z=new G($,U,{rate:_,volume:S,pitch:N,boundary:F,proxy:O,connectTimeoutSeconds:r,receiveTimeoutSeconds:C});for await(let K of z.stream())yield K}async function*h0(E){let{text:$,voice:U="en-US-EmmaMultilingualNeural",rate:_="+0%",volume:S="+0%",pitch:N="+0Hz",boundary:F="WordBoundary",proxy:O,connectTimeoutSeconds:r,receiveTimeoutSeconds:C,subtitlePath:z}=E,K=new G($,U,{rate:_,volume:S,pitch:N,boundary:F,proxy:O,connectTimeoutSeconds:r,receiveTimeoutSeconds:C}),H=new X,Y=A.dirname(z);if(!w.existsSync(Y))w.mkdirSync(Y,{recursive:!0});for await(let I of K.stream())if(I.type==="audio"&&I.data)yield I;else if(I.type==="WordBoundary"||I.type==="SentenceBoundary"){H.feed(I);let J=H.getSrt();w.writeFileSync(z,J),yield{...I,subtitles:J}}}async function*c0(E){let{outputPath:$,...U}=E,_=A.dirname($);if(!w.existsSync(_))w.mkdirSync(_,{recursive:!0});let S=w.createWriteStream($),N=0;try{for await(let F of C0(U))if(F.type==="audio"&&F.data)S.write(F.data),N+=F.data.length,yield{bytesWritten:N,chunkSize:F.data.length}}finally{S.end()}}export{h0 as streamSpeechWithSubtitlesToFile,c0 as streamSpeechToFile,C0 as streamSpeech,v0 as getVoices,b0 as generateSpeechWithSubtitlesToFile,y0 as generateSpeechToFile,H0 as generateSpeech,l0 as findVoices,x0 as Experimental_Raw};
package/package.json CHANGED
@@ -22,7 +22,7 @@
22
22
  "README.md"
23
23
  ],
24
24
  "homepage": "https://github.com/The-Best-Codes/edge-tts",
25
- "version": "2.0.0",
25
+ "version": "3.0.0",
26
26
  "author": {
27
27
  "name": "The-Best-Codes",
28
28
  "url": "https://bestcodes.dev"