@ssml-builder-js/azure-tts-client 2.10.0 → 2.12.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/CHANGELOG.md +12 -0
- package/dist/index.d.mts +54 -1
- package/dist/index.d.ts +54 -1
- package/dist/index.js +125 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +122 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +10 -2
- package/src/index.ts +17 -1
- package/src/synthesis.ts +41 -4
- package/src/types.ts +29 -0
- package/src/voiceCatalog.ts +120 -0
- package/test/synthesis.test.ts +40 -1
- package/test/voice-catalog.test.ts +51 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @ssml-builder-js/azure-tts-client
|
|
2
2
|
|
|
3
|
+
## 2.12.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Add `synthesizeSsml` and `SsmlSynthesisResult` for word boundaries, visemes, bookmarks, and duration metadata.
|
|
8
|
+
|
|
9
|
+
## 2.11.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- Release v2.11.0 with Azure voice catalog APIs and synchronization tools, expanded editor voice support, and stricter background audio validation.
|
|
14
|
+
|
|
3
15
|
## 2.10.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/dist/index.d.mts
CHANGED
|
@@ -6,6 +6,31 @@ interface TtsConfig {
|
|
|
6
6
|
region: string;
|
|
7
7
|
outputFormat?: string;
|
|
8
8
|
}
|
|
9
|
+
interface SsmlSynthesisBoundary {
|
|
10
|
+
text: string;
|
|
11
|
+
audioOffsetMs: number;
|
|
12
|
+
durationMs: number;
|
|
13
|
+
}
|
|
14
|
+
interface SsmlSynthesisViseme {
|
|
15
|
+
visemeId: number;
|
|
16
|
+
audioOffsetMs: number;
|
|
17
|
+
}
|
|
18
|
+
interface SsmlSynthesisBookmark {
|
|
19
|
+
name: string;
|
|
20
|
+
audioOffsetMs: number;
|
|
21
|
+
}
|
|
22
|
+
/** Audio and Azure Speech synchronization events emitted for one SSML request. */
|
|
23
|
+
interface SsmlSynthesisResult {
|
|
24
|
+
audioData: ArrayBuffer;
|
|
25
|
+
durationMs: number;
|
|
26
|
+
boundaries?: SsmlSynthesisBoundary[];
|
|
27
|
+
/** Alias matching the Azure Speech event name. */
|
|
28
|
+
wordBoundary?: SsmlSynthesisBoundary[];
|
|
29
|
+
/** Alias for consumers that use Azure's word-boundary terminology. */
|
|
30
|
+
wordBoundaries?: SsmlSynthesisBoundary[];
|
|
31
|
+
visemes?: SsmlSynthesisViseme[];
|
|
32
|
+
bookmarks?: SsmlSynthesisBookmark[];
|
|
33
|
+
}
|
|
9
34
|
interface AzureTtsLogger {
|
|
10
35
|
debug?: (...args: unknown[]) => void;
|
|
11
36
|
info?: (...args: unknown[]) => void;
|
|
@@ -38,8 +63,36 @@ declare class AzureTtsClient {
|
|
|
38
63
|
#private;
|
|
39
64
|
constructor(options: AzureTtsClientOptions);
|
|
40
65
|
synthesize(ssml: string): Promise<ArrayBuffer>;
|
|
66
|
+
synthesizeSsml(ssml: string): Promise<SsmlSynthesisResult>;
|
|
41
67
|
}
|
|
42
68
|
|
|
69
|
+
declare function synthesizeSsml(ssml: string, config: TtsConfig): Promise<SsmlSynthesisResult>;
|
|
70
|
+
/** Backward-compatible audio-only synthesis helper. */
|
|
43
71
|
declare function synthesizeSpeech(ssml: string, config: TtsConfig): Promise<ArrayBuffer>;
|
|
44
72
|
|
|
45
|
-
|
|
73
|
+
interface FetchAzureVoiceCatalogOptions {
|
|
74
|
+
apiKey: string;
|
|
75
|
+
region: string | string[];
|
|
76
|
+
}
|
|
77
|
+
interface AzureVoiceCatalogVoice {
|
|
78
|
+
name: string;
|
|
79
|
+
locale: string;
|
|
80
|
+
secondaryLocales?: readonly string[];
|
|
81
|
+
styles?: readonly string[];
|
|
82
|
+
regions: readonly string[];
|
|
83
|
+
status?: "ga" | "preview" | "deprecated";
|
|
84
|
+
}
|
|
85
|
+
interface FetchedAzureVoiceCatalogMetadata {
|
|
86
|
+
voiceCount: number;
|
|
87
|
+
generatedAt: string;
|
|
88
|
+
apiVersion: string;
|
|
89
|
+
regions: readonly string[];
|
|
90
|
+
}
|
|
91
|
+
interface AzureVoiceCatalog {
|
|
92
|
+
voices: readonly AzureVoiceCatalogVoice[];
|
|
93
|
+
metadata: FetchedAzureVoiceCatalogMetadata;
|
|
94
|
+
}
|
|
95
|
+
/** Fetches and deduplicates the current Azure Speech voice catalog for one or more regions. */
|
|
96
|
+
declare function fetchAzureVoiceCatalog(options: FetchAzureVoiceCatalogOptions): Promise<AzureVoiceCatalog>;
|
|
97
|
+
|
|
98
|
+
export { AzureTtsClient, type AzureTtsClientOptions, AzureTtsError, type AzureTtsLogger, AzureTtsSdkError, type AzureVoiceCatalog, type AzureVoiceCatalogVoice, type FetchAzureVoiceCatalogOptions, type FetchedAzureVoiceCatalogMetadata, type SsmlSynthesisBookmark, type SsmlSynthesisBoundary, type SsmlSynthesisResult, type SsmlSynthesisViseme, type TtsConfig, fetchAzureVoiceCatalog, synthesizeSpeech, synthesizeSsml };
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,31 @@ interface TtsConfig {
|
|
|
6
6
|
region: string;
|
|
7
7
|
outputFormat?: string;
|
|
8
8
|
}
|
|
9
|
+
interface SsmlSynthesisBoundary {
|
|
10
|
+
text: string;
|
|
11
|
+
audioOffsetMs: number;
|
|
12
|
+
durationMs: number;
|
|
13
|
+
}
|
|
14
|
+
interface SsmlSynthesisViseme {
|
|
15
|
+
visemeId: number;
|
|
16
|
+
audioOffsetMs: number;
|
|
17
|
+
}
|
|
18
|
+
interface SsmlSynthesisBookmark {
|
|
19
|
+
name: string;
|
|
20
|
+
audioOffsetMs: number;
|
|
21
|
+
}
|
|
22
|
+
/** Audio and Azure Speech synchronization events emitted for one SSML request. */
|
|
23
|
+
interface SsmlSynthesisResult {
|
|
24
|
+
audioData: ArrayBuffer;
|
|
25
|
+
durationMs: number;
|
|
26
|
+
boundaries?: SsmlSynthesisBoundary[];
|
|
27
|
+
/** Alias matching the Azure Speech event name. */
|
|
28
|
+
wordBoundary?: SsmlSynthesisBoundary[];
|
|
29
|
+
/** Alias for consumers that use Azure's word-boundary terminology. */
|
|
30
|
+
wordBoundaries?: SsmlSynthesisBoundary[];
|
|
31
|
+
visemes?: SsmlSynthesisViseme[];
|
|
32
|
+
bookmarks?: SsmlSynthesisBookmark[];
|
|
33
|
+
}
|
|
9
34
|
interface AzureTtsLogger {
|
|
10
35
|
debug?: (...args: unknown[]) => void;
|
|
11
36
|
info?: (...args: unknown[]) => void;
|
|
@@ -38,8 +63,36 @@ declare class AzureTtsClient {
|
|
|
38
63
|
#private;
|
|
39
64
|
constructor(options: AzureTtsClientOptions);
|
|
40
65
|
synthesize(ssml: string): Promise<ArrayBuffer>;
|
|
66
|
+
synthesizeSsml(ssml: string): Promise<SsmlSynthesisResult>;
|
|
41
67
|
}
|
|
42
68
|
|
|
69
|
+
declare function synthesizeSsml(ssml: string, config: TtsConfig): Promise<SsmlSynthesisResult>;
|
|
70
|
+
/** Backward-compatible audio-only synthesis helper. */
|
|
43
71
|
declare function synthesizeSpeech(ssml: string, config: TtsConfig): Promise<ArrayBuffer>;
|
|
44
72
|
|
|
45
|
-
|
|
73
|
+
interface FetchAzureVoiceCatalogOptions {
|
|
74
|
+
apiKey: string;
|
|
75
|
+
region: string | string[];
|
|
76
|
+
}
|
|
77
|
+
interface AzureVoiceCatalogVoice {
|
|
78
|
+
name: string;
|
|
79
|
+
locale: string;
|
|
80
|
+
secondaryLocales?: readonly string[];
|
|
81
|
+
styles?: readonly string[];
|
|
82
|
+
regions: readonly string[];
|
|
83
|
+
status?: "ga" | "preview" | "deprecated";
|
|
84
|
+
}
|
|
85
|
+
interface FetchedAzureVoiceCatalogMetadata {
|
|
86
|
+
voiceCount: number;
|
|
87
|
+
generatedAt: string;
|
|
88
|
+
apiVersion: string;
|
|
89
|
+
regions: readonly string[];
|
|
90
|
+
}
|
|
91
|
+
interface AzureVoiceCatalog {
|
|
92
|
+
voices: readonly AzureVoiceCatalogVoice[];
|
|
93
|
+
metadata: FetchedAzureVoiceCatalogMetadata;
|
|
94
|
+
}
|
|
95
|
+
/** Fetches and deduplicates the current Azure Speech voice catalog for one or more regions. */
|
|
96
|
+
declare function fetchAzureVoiceCatalog(options: FetchAzureVoiceCatalogOptions): Promise<AzureVoiceCatalog>;
|
|
97
|
+
|
|
98
|
+
export { AzureTtsClient, type AzureTtsClientOptions, AzureTtsError, type AzureTtsLogger, AzureTtsSdkError, type AzureVoiceCatalog, type AzureVoiceCatalogVoice, type FetchAzureVoiceCatalogOptions, type FetchedAzureVoiceCatalogMetadata, type SsmlSynthesisBookmark, type SsmlSynthesisBoundary, type SsmlSynthesisResult, type SsmlSynthesisViseme, type TtsConfig, fetchAzureVoiceCatalog, synthesizeSpeech, synthesizeSsml };
|
package/dist/index.js
CHANGED
|
@@ -40,7 +40,9 @@ __export(index_exports, {
|
|
|
40
40
|
AzureTtsClient: () => AzureTtsClient,
|
|
41
41
|
AzureTtsError: () => AzureTtsError,
|
|
42
42
|
AzureTtsSdkError: () => AzureTtsSdkError,
|
|
43
|
-
|
|
43
|
+
fetchAzureVoiceCatalog: () => fetchAzureVoiceCatalog,
|
|
44
|
+
synthesizeSpeech: () => synthesizeSpeech,
|
|
45
|
+
synthesizeSsml: () => synthesizeSsml
|
|
44
46
|
});
|
|
45
47
|
module.exports = __toCommonJS(index_exports);
|
|
46
48
|
|
|
@@ -150,7 +152,8 @@ function closeSpeechResources(speechConfig, synthesizer) {
|
|
|
150
152
|
} catch {
|
|
151
153
|
}
|
|
152
154
|
}
|
|
153
|
-
|
|
155
|
+
var ticksToMilliseconds = (ticks) => Math.max(0, ticks) / 1e4;
|
|
156
|
+
async function synthesizeSsml(ssml, config) {
|
|
154
157
|
if (config.signal?.aborted) {
|
|
155
158
|
throw createSpeechSdkError("Speech synthesis was cancelled.");
|
|
156
159
|
}
|
|
@@ -177,6 +180,22 @@ async function synthesizeSpeech(ssml, config) {
|
|
|
177
180
|
closeResources();
|
|
178
181
|
reject(createSpeechSdkError(error));
|
|
179
182
|
};
|
|
183
|
+
const boundaries = [];
|
|
184
|
+
const visemes = [];
|
|
185
|
+
const bookmarks = [];
|
|
186
|
+
synthesizer.wordBoundary = (_sender, event) => {
|
|
187
|
+
boundaries.push({
|
|
188
|
+
text: event.text,
|
|
189
|
+
audioOffsetMs: ticksToMilliseconds(event.audioOffset),
|
|
190
|
+
durationMs: ticksToMilliseconds(event.duration)
|
|
191
|
+
});
|
|
192
|
+
};
|
|
193
|
+
synthesizer.visemeReceived = (_sender, event) => {
|
|
194
|
+
visemes.push({ visemeId: event.visemeId, audioOffsetMs: ticksToMilliseconds(event.audioOffset) });
|
|
195
|
+
};
|
|
196
|
+
synthesizer.bookmarkReached = (_sender, event) => {
|
|
197
|
+
bookmarks.push({ name: event.text, audioOffsetMs: ticksToMilliseconds(event.audioOffset) });
|
|
198
|
+
};
|
|
180
199
|
const cb = (result) => {
|
|
181
200
|
if (settled) return;
|
|
182
201
|
const { reason, errorDetails } = result;
|
|
@@ -188,7 +207,20 @@ async function synthesizeSpeech(ssml, config) {
|
|
|
188
207
|
settled = true;
|
|
189
208
|
cleanup();
|
|
190
209
|
closeResources();
|
|
191
|
-
|
|
210
|
+
const eventDurationMs = Math.max(
|
|
211
|
+
0,
|
|
212
|
+
...(boundaries ?? []).map((boundary) => boundary.audioOffsetMs + boundary.durationMs),
|
|
213
|
+
...(visemes ?? []).map((viseme) => viseme.audioOffsetMs),
|
|
214
|
+
...(bookmarks ?? []).map((bookmark) => bookmark.audioOffsetMs)
|
|
215
|
+
);
|
|
216
|
+
const durationMs = result.audioDuration ? ticksToMilliseconds(result.audioDuration) : eventDurationMs;
|
|
217
|
+
resolve({
|
|
218
|
+
audioData: result.audioData,
|
|
219
|
+
durationMs,
|
|
220
|
+
...boundaries.length > 0 ? { boundaries, wordBoundary: boundaries, wordBoundaries: boundaries } : {},
|
|
221
|
+
...visemes.length > 0 ? { visemes } : {},
|
|
222
|
+
...bookmarks.length > 0 ? { bookmarks } : {}
|
|
223
|
+
});
|
|
192
224
|
};
|
|
193
225
|
try {
|
|
194
226
|
if (config.signal) {
|
|
@@ -207,6 +239,9 @@ async function synthesizeSpeech(ssml, config) {
|
|
|
207
239
|
}
|
|
208
240
|
});
|
|
209
241
|
}
|
|
242
|
+
async function synthesizeSpeech(ssml, config) {
|
|
243
|
+
return (await synthesizeSsml(ssml, config)).audioData;
|
|
244
|
+
}
|
|
210
245
|
|
|
211
246
|
// src/client.ts
|
|
212
247
|
var ENDPOINT_TEMPLATE = "https://{region}.tts.speech.microsoft.com/cognitiveservices/v1";
|
|
@@ -223,13 +258,99 @@ var AzureTtsClient = class {
|
|
|
223
258
|
const config = { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs };
|
|
224
259
|
return synthesizeSpeech(ssml, config);
|
|
225
260
|
}
|
|
261
|
+
async synthesizeSsml(ssml) {
|
|
262
|
+
const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
|
|
263
|
+
const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
264
|
+
__privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
|
|
265
|
+
return synthesizeSsml(ssml, { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs });
|
|
266
|
+
}
|
|
226
267
|
};
|
|
227
268
|
_options = new WeakMap();
|
|
269
|
+
|
|
270
|
+
// src/voiceCatalog.ts
|
|
271
|
+
var AZURE_VOICE_API_VERSION = "2025-10-01";
|
|
272
|
+
function stringValue(value) {
|
|
273
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
274
|
+
}
|
|
275
|
+
function stringList(value) {
|
|
276
|
+
if (!Array.isArray(value)) return [];
|
|
277
|
+
return [...new Set(value.map(stringValue).filter((item) => item !== void 0))];
|
|
278
|
+
}
|
|
279
|
+
function normalizeStatus(value) {
|
|
280
|
+
const status = stringValue(value)?.toLowerCase();
|
|
281
|
+
if (status === "preview" || status === "deprecated" || status === "ga") return status;
|
|
282
|
+
return void 0;
|
|
283
|
+
}
|
|
284
|
+
function normalizeRegions(region) {
|
|
285
|
+
const regions = Array.isArray(region) ? region : [region];
|
|
286
|
+
const result = [...new Set(regions.map((item) => item.trim()).filter(Boolean))];
|
|
287
|
+
if (result.length === 0) throw new TypeError("At least one Azure Speech region is required.");
|
|
288
|
+
return result;
|
|
289
|
+
}
|
|
290
|
+
async function fetchRegionVoices(region, apiKey) {
|
|
291
|
+
const endpoint = `https://${encodeURIComponent(region)}.tts.speech.microsoft.com/cognitiveservices/voices/list`;
|
|
292
|
+
const response = await fetch(endpoint, {
|
|
293
|
+
headers: {
|
|
294
|
+
Accept: "application/json",
|
|
295
|
+
"Ocp-Apim-Subscription-Key": apiKey
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
if (!response.ok) {
|
|
299
|
+
throw new Error(`Azure List Voices API request failed for region "${region}" with HTTP ${response.status}.`);
|
|
300
|
+
}
|
|
301
|
+
const payload = await response.json();
|
|
302
|
+
if (!Array.isArray(payload)) throw new Error(`Azure List Voices API returned an invalid response for "${region}".`);
|
|
303
|
+
return payload.filter((item) => Boolean(item && typeof item === "object"));
|
|
304
|
+
}
|
|
305
|
+
async function fetchAzureVoiceCatalog(options) {
|
|
306
|
+
if (!options || typeof options.apiKey !== "string" || !options.apiKey.trim())
|
|
307
|
+
throw new TypeError("An Azure Speech API key is required.");
|
|
308
|
+
const regions = normalizeRegions(options.region);
|
|
309
|
+
const payloads = await Promise.all(regions.map((region) => fetchRegionVoices(region, options.apiKey)));
|
|
310
|
+
const voices = /* @__PURE__ */ new Map();
|
|
311
|
+
for (let regionIndex = 0; regionIndex < payloads.length; regionIndex += 1) {
|
|
312
|
+
const region = regions[regionIndex];
|
|
313
|
+
for (const record of payloads[regionIndex]) {
|
|
314
|
+
const name = stringValue(record.ShortName) ?? stringValue(record.Name);
|
|
315
|
+
const locale = stringValue(record.Locale);
|
|
316
|
+
if (!name || !locale) continue;
|
|
317
|
+
const key = name.toLowerCase();
|
|
318
|
+
const existing = voices.get(key);
|
|
319
|
+
const secondaryLocales = stringList(record.SecondaryLocaleList);
|
|
320
|
+
const styles = stringList(record.StyleList);
|
|
321
|
+
const status = normalizeStatus(record.Status);
|
|
322
|
+
const merged = {
|
|
323
|
+
name: existing?.name ?? name,
|
|
324
|
+
locale: existing?.locale ?? locale,
|
|
325
|
+
regions: [.../* @__PURE__ */ new Set([...existing?.regions ?? [], region])]
|
|
326
|
+
};
|
|
327
|
+
const mergedSecondaryLocales = [.../* @__PURE__ */ new Set([...existing?.secondaryLocales ?? [], ...secondaryLocales])];
|
|
328
|
+
if (mergedSecondaryLocales.length > 0) merged.secondaryLocales = mergedSecondaryLocales;
|
|
329
|
+
const mergedStyles = [.../* @__PURE__ */ new Set([...existing?.styles ?? [], ...styles])];
|
|
330
|
+
if (mergedStyles.length > 0) merged.styles = mergedStyles;
|
|
331
|
+
if (status) merged.status = status;
|
|
332
|
+
else if (existing?.status) merged.status = existing.status;
|
|
333
|
+
voices.set(key, merged);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
const sortedVoices = [...voices.values()].sort((first, second) => first.name.localeCompare(second.name));
|
|
337
|
+
return {
|
|
338
|
+
voices: sortedVoices,
|
|
339
|
+
metadata: {
|
|
340
|
+
voiceCount: sortedVoices.length,
|
|
341
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
342
|
+
apiVersion: AZURE_VOICE_API_VERSION,
|
|
343
|
+
regions
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
}
|
|
228
347
|
// Annotate the CommonJS export names for ESM import in node:
|
|
229
348
|
0 && (module.exports = {
|
|
230
349
|
AzureTtsClient,
|
|
231
350
|
AzureTtsError,
|
|
232
351
|
AzureTtsSdkError,
|
|
233
|
-
|
|
352
|
+
fetchAzureVoiceCatalog,
|
|
353
|
+
synthesizeSpeech,
|
|
354
|
+
synthesizeSsml
|
|
234
355
|
});
|
|
235
356
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/synthesis.ts","../src/speechConfig.ts","../src/outputFormats.ts","../src/client.ts"],"sourcesContent":["/**\n * azure-tts-client: Azure Text-to-Speech client for SSML playback.\n */\n\nexport type { AzureTtsClientOptions, AzureTtsLogger, TtsConfig } from \"./types.ts\";\nexport { AzureTtsError, AzureTtsSdkError } from \"./errors.ts\";\nexport { AzureTtsClient } from \"./client.ts\";\nexport { synthesizeSpeech } from \"./synthesis.ts\";\n","export class AzureTtsError extends Error {\n readonly status: number;\n readonly statusText: string;\n readonly responseBody: string;\n readonly requestId: string | null;\n\n constructor(status: number, statusText: string, responseBody: string, requestId: string | null) {\n super(`Azure TTS request failed: ${status} ${statusText}`);\n this.name = \"AzureTtsError\";\n this.status = status;\n this.statusText = statusText;\n this.responseBody = responseBody;\n this.requestId = requestId;\n }\n}\n\nexport class AzureTtsSdkError extends AzureTtsError {\n readonly errorDetails: string;\n\n constructor(errorDetails: string) {\n super(0, \"Speech SDK\", errorDetails, null);\n this.name = \"AzureTtsSdkError\";\n this.message = `Azure TTS synthesis failed: ${errorDetails}`;\n this.errorDetails = errorDetails;\n }\n}\n\nexport function createSpeechSdkError(error: unknown): AzureTtsSdkError {\n const message = error instanceof Error ? error.message : String(error);\n return new AzureTtsSdkError(message);\n}\n","import * as SpeechSDK from \"microsoft-cognitiveservices-speech-sdk\";\nimport { createSpeechSdkError } from \"./errors.ts\";\nimport { createSpeechConfig } from \"./speechConfig.ts\";\nimport type { TtsConfig } from \"./types.ts\";\n\nfunction closeSpeechResources(speechConfig: SpeechSDK.SpeechConfig, synthesizer: SpeechSDK.SpeechSynthesizer): void {\n try {\n synthesizer.close();\n } catch {}\n\n try {\n speechConfig.close();\n } catch {}\n}\n\nexport async function synthesizeSpeech(ssml: string, config: TtsConfig): Promise<ArrayBuffer> {\n if (config.signal?.aborted) {\n throw createSpeechSdkError(\"Speech synthesis was cancelled.\");\n }\n\n const speechConfig = createSpeechConfig(config);\n const synthesizer = new SpeechSDK.SpeechSynthesizer(speechConfig, null);\n\n return await new Promise<ArrayBuffer>((resolve, reject) => {\n let resourcesClosed = false;\n let settled = false;\n let timeout: ReturnType<typeof setTimeout> | undefined;\n let abortHandler: (() => void) | undefined;\n const cleanup = () => {\n if (timeout) clearTimeout(timeout);\n if (abortHandler) config.signal?.removeEventListener(\"abort\", abortHandler);\n };\n const closeResources = () => {\n if (resourcesClosed) return;\n resourcesClosed = true;\n closeSpeechResources(speechConfig, synthesizer);\n };\n const rejectWithError = (error: unknown) => {\n if (settled) return;\n settled = true;\n cleanup();\n closeResources();\n reject(createSpeechSdkError(error));\n };\n\n const cb = (result: SpeechSDK.SpeechSynthesisResult) => {\n if (settled) return;\n const { reason, errorDetails } = result;\n if (reason !== SpeechSDK.ResultReason.SynthesizingAudioCompleted) {\n const err = errorDetails || `Speech synthesis failed with reason ${reason}.`;\n rejectWithError(err);\n return;\n }\n settled = true;\n cleanup();\n closeResources();\n resolve(result.audioData);\n };\n\n try {\n if (config.signal) {\n abortHandler = () => rejectWithError(\"Speech synthesis was cancelled.\");\n config.signal.addEventListener(\"abort\", abortHandler, { once: true });\n }\n if (config.timeoutMs !== undefined && config.timeoutMs > 0) {\n timeout = setTimeout(\n () => rejectWithError(`Speech synthesis timed out after ${config.timeoutMs} ms.`),\n config.timeoutMs,\n );\n }\n synthesizer.speakSsmlAsync(ssml, cb, rejectWithError);\n } catch (error) {\n rejectWithError(error);\n }\n });\n}\n","import { SpeechConfig } from \"microsoft-cognitiveservices-speech-sdk\";\nimport { DEFAULT_OUTPUT_FORMAT, resolveOutputFormat } from \"./outputFormats.ts\";\nimport type { TtsConfig } from \"./types.ts\";\n\nexport function resolveEndpoint(config: TtsConfig): string {\n const endpoint = config.endpoint?.trim() || \"https://{region}.tts.speech.microsoft.com/cognitiveservices/v1\";\n return endpoint.replace(/\\{region\\}/g, encodeURIComponent(config.region));\n}\n\nexport function createSpeechConfig(config: TtsConfig): SpeechConfig {\n const { outputFormat = DEFAULT_OUTPUT_FORMAT, subscriptionKey } = config;\n\n const endpoint = new URL(resolveEndpoint(config));\n const speechConfig = SpeechConfig.fromEndpoint(endpoint, subscriptionKey);\n speechConfig.speechSynthesisOutputFormat = resolveOutputFormat(outputFormat);\n return speechConfig;\n}\n","import * as SpeechSDK from \"microsoft-cognitiveservices-speech-sdk\";\n\nexport const DEFAULT_OUTPUT_FORMAT = \"audio-16khz-128kbitrate-mono-mp3\";\n\nconst OUTPUT_FORMATS: Record<string, SpeechSDK.SpeechSynthesisOutputFormat> = {\n \"raw-8khz-8bit-mono-mulaw\": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz8BitMonoMULaw,\n \"riff-16khz-16kbps-mono-siren\": SpeechSDK.SpeechSynthesisOutputFormat.Riff16Khz16KbpsMonoSiren,\n \"audio-16khz-16kbps-mono-siren\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz16KbpsMonoSiren,\n \"audio-16khz-32kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz32KBitRateMonoMp3,\n \"audio-16khz-128kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz128KBitRateMonoMp3,\n \"audio-16khz-64kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz64KBitRateMonoMp3,\n \"audio-24khz-48kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz48KBitRateMonoMp3,\n \"audio-24khz-96kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz96KBitRateMonoMp3,\n \"audio-24khz-160kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz160KBitRateMonoMp3,\n \"raw-16khz-16bit-mono-truesilk\": SpeechSDK.SpeechSynthesisOutputFormat.Raw16Khz16BitMonoTrueSilk,\n \"riff-16khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff16Khz16BitMonoPcm,\n \"riff-8khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz16BitMonoPcm,\n \"riff-24khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff24Khz16BitMonoPcm,\n \"riff-8khz-8bit-mono-mulaw\": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz8BitMonoMULaw,\n \"raw-16khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw16Khz16BitMonoPcm,\n \"raw-24khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw24Khz16BitMonoPcm,\n \"raw-8khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz16BitMonoPcm,\n \"ogg-16khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Ogg16Khz16BitMonoOpus,\n \"ogg-24khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Ogg24Khz16BitMonoOpus,\n \"raw-48khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw48Khz16BitMonoPcm,\n \"riff-48khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff48Khz16BitMonoPcm,\n \"audio-48khz-96kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio48Khz96KBitRateMonoMp3,\n \"audio-48khz-192kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio48Khz192KBitRateMonoMp3,\n \"ogg-48khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Ogg48Khz16BitMonoOpus,\n \"webm-16khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Webm16Khz16BitMonoOpus,\n \"webm-24khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Webm24Khz16BitMonoOpus,\n \"webm-24khz-16bit-24kbps-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Webm24Khz16Bit24KbpsMonoOpus,\n \"raw-24khz-16bit-mono-truesilk\": SpeechSDK.SpeechSynthesisOutputFormat.Raw24Khz16BitMonoTrueSilk,\n \"raw-8khz-8bit-mono-alaw\": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz8BitMonoALaw,\n \"riff-8khz-8bit-mono-alaw\": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz8BitMonoALaw,\n \"audio-16khz-16bit-32kbps-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz16Bit32KbpsMonoOpus,\n \"audio-24khz-16bit-48kbps-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz16Bit48KbpsMonoOpus,\n \"audio-24khz-16bit-24kbps-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz16Bit24KbpsMonoOpus,\n \"raw-22050hz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw22050Hz16BitMonoPcm,\n \"riff-22050hz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff22050Hz16BitMonoPcm,\n \"raw-44100hz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw44100Hz16BitMonoPcm,\n \"riff-44100hz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff44100Hz16BitMonoPcm,\n \"amr-wb-16000hz\": SpeechSDK.SpeechSynthesisOutputFormat.AmrWb16000Hz,\n \"g722-16khz-64kbps\": SpeechSDK.SpeechSynthesisOutputFormat.G72216Khz64Kbps,\n};\n\nexport function resolveOutputFormat(outputFormat: string): SpeechSDK.SpeechSynthesisOutputFormat {\n const resolvedFormat = OUTPUT_FORMATS[outputFormat];\n if (resolvedFormat === undefined) {\n throw new Error(`Unsupported Azure Speech output format: ${outputFormat}`);\n }\n\n return resolvedFormat;\n}\n","import { synthesizeSpeech } from \"./synthesis.ts\";\nimport type { AzureTtsClientOptions } from \"./types.ts\";\n\nconst ENDPOINT_TEMPLATE = \"https://{region}.tts.speech.microsoft.com/cognitiveservices/v1\";\n\nexport class AzureTtsClient {\n readonly #options: AzureTtsClientOptions;\n\n constructor(options: AzureTtsClientOptions) {\n this.#options = options;\n }\n\n async synthesize(ssml: string): Promise<ArrayBuffer> {\n const { region, subscriptionKey, outputFormat, signal, timeoutMs } = this.#options;\n const endpoint = this.#options.endpoint?.trim() || ENDPOINT_TEMPLATE.replace(\"{region}\", region);\n this.#options.logger?.debug?.(\"Using Azure TTS endpoint:\", endpoint);\n\n const config = { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs };\n return synthesizeSpeech(ssml, config);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAO,IAAM,gBAAN,cAA4B,MAAM;AAAA,EAMvC,YAAY,QAAgB,YAAoB,cAAsB,WAA0B;AAC9F,UAAM,6BAA6B,MAAM,IAAI,UAAU,EAAE;AACzD,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,aAAa;AAClB,SAAK,eAAe;AACpB,SAAK,YAAY;AAAA,EACnB;AACF;AAEO,IAAM,mBAAN,cAA+B,cAAc;AAAA,EAGlD,YAAY,cAAsB;AAChC,UAAM,GAAG,cAAc,cAAc,IAAI;AACzC,SAAK,OAAO;AACZ,SAAK,UAAU,+BAA+B,YAAY;AAC1D,SAAK,eAAe;AAAA,EACtB;AACF;AAEO,SAAS,qBAAqB,OAAkC;AACrE,QAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,SAAO,IAAI,iBAAiB,OAAO;AACrC;;;AC9BA,IAAAA,aAA2B;;;ACA3B,oDAA6B;;;ACA7B,gBAA2B;AAEpB,IAAM,wBAAwB;AAErC,IAAM,iBAAwE;AAAA,EAC5E,4BAAsC,sCAA4B;AAAA,EAClE,gCAA0C,sCAA4B;AAAA,EACtE,iCAA2C,sCAA4B;AAAA,EACvE,mCAA6C,sCAA4B;AAAA,EACzE,oCAA8C,sCAA4B;AAAA,EAC1E,mCAA6C,sCAA4B;AAAA,EACzE,mCAA6C,sCAA4B;AAAA,EACzE,mCAA6C,sCAA4B;AAAA,EACzE,oCAA8C,sCAA4B;AAAA,EAC1E,iCAA2C,sCAA4B;AAAA,EACvE,6BAAuC,sCAA4B;AAAA,EACnE,4BAAsC,sCAA4B;AAAA,EAClE,6BAAuC,sCAA4B;AAAA,EACnE,6BAAuC,sCAA4B;AAAA,EACnE,4BAAsC,sCAA4B;AAAA,EAClE,4BAAsC,sCAA4B;AAAA,EAClE,2BAAqC,sCAA4B;AAAA,EACjE,6BAAuC,sCAA4B;AAAA,EACnE,6BAAuC,sCAA4B;AAAA,EACnE,4BAAsC,sCAA4B;AAAA,EAClE,6BAAuC,sCAA4B;AAAA,EACnE,mCAA6C,sCAA4B;AAAA,EACzE,oCAA8C,sCAA4B;AAAA,EAC1E,6BAAuC,sCAA4B;AAAA,EACnE,8BAAwC,sCAA4B;AAAA,EACpE,8BAAwC,sCAA4B;AAAA,EACpE,qCAA+C,sCAA4B;AAAA,EAC3E,iCAA2C,sCAA4B;AAAA,EACvE,2BAAqC,sCAA4B;AAAA,EACjE,4BAAsC,sCAA4B;AAAA,EAClE,sCAAgD,sCAA4B;AAAA,EAC5E,sCAAgD,sCAA4B;AAAA,EAC5E,sCAAgD,sCAA4B;AAAA,EAC5E,8BAAwC,sCAA4B;AAAA,EACpE,+BAAyC,sCAA4B;AAAA,EACrE,8BAAwC,sCAA4B;AAAA,EACpE,+BAAyC,sCAA4B;AAAA,EACrE,kBAA4B,sCAA4B;AAAA,EACxD,qBAA+B,sCAA4B;AAC7D;AAEO,SAAS,oBAAoB,cAA6D;AAC/F,QAAM,iBAAiB,eAAe,YAAY;AAClD,MAAI,mBAAmB,QAAW;AAChC,UAAM,IAAI,MAAM,2CAA2C,YAAY,EAAE;AAAA,EAC3E;AAEA,SAAO;AACT;;;ADjDO,SAAS,gBAAgB,QAA2B;AACzD,QAAM,WAAW,OAAO,UAAU,KAAK,KAAK;AAC5C,SAAO,SAAS,QAAQ,eAAe,mBAAmB,OAAO,MAAM,CAAC;AAC1E;AAEO,SAAS,mBAAmB,QAAiC;AAClE,QAAM,EAAE,eAAe,uBAAuB,gBAAgB,IAAI;AAElE,QAAM,WAAW,IAAI,IAAI,gBAAgB,MAAM,CAAC;AAChD,QAAM,eAAe,2DAAa,aAAa,UAAU,eAAe;AACxE,eAAa,8BAA8B,oBAAoB,YAAY;AAC3E,SAAO;AACT;;;ADXA,SAAS,qBAAqB,cAAsC,aAAgD;AAClH,MAAI;AACF,gBAAY,MAAM;AAAA,EACpB,QAAQ;AAAA,EAAC;AAET,MAAI;AACF,iBAAa,MAAM;AAAA,EACrB,QAAQ;AAAA,EAAC;AACX;AAEA,eAAsB,iBAAiB,MAAc,QAAyC;AAC5F,MAAI,OAAO,QAAQ,SAAS;AAC1B,UAAM,qBAAqB,iCAAiC;AAAA,EAC9D;AAEA,QAAM,eAAe,mBAAmB,MAAM;AAC9C,QAAM,cAAc,IAAc,6BAAkB,cAAc,IAAI;AAEtE,SAAO,MAAM,IAAI,QAAqB,CAAC,SAAS,WAAW;AACzD,QAAI,kBAAkB;AACtB,QAAI,UAAU;AACd,QAAI;AACJ,QAAI;AACJ,UAAM,UAAU,MAAM;AACpB,UAAI,QAAS,cAAa,OAAO;AACjC,UAAI,aAAc,QAAO,QAAQ,oBAAoB,SAAS,YAAY;AAAA,IAC5E;AACA,UAAM,iBAAiB,MAAM;AAC3B,UAAI,gBAAiB;AACrB,wBAAkB;AAClB,2BAAqB,cAAc,WAAW;AAAA,IAChD;AACA,UAAM,kBAAkB,CAAC,UAAmB;AAC1C,UAAI,QAAS;AACb,gBAAU;AACV,cAAQ;AACR,qBAAe;AACf,aAAO,qBAAqB,KAAK,CAAC;AAAA,IACpC;AAEA,UAAM,KAAK,CAAC,WAA4C;AACtD,UAAI,QAAS;AACb,YAAM,EAAE,QAAQ,aAAa,IAAI;AACjC,UAAI,WAAqB,wBAAa,4BAA4B;AAChE,cAAM,MAAM,gBAAgB,uCAAuC,MAAM;AACzE,wBAAgB,GAAG;AACnB;AAAA,MACF;AACA,gBAAU;AACV,cAAQ;AACR,qBAAe;AACf,cAAQ,OAAO,SAAS;AAAA,IAC1B;AAEA,QAAI;AACF,UAAI,OAAO,QAAQ;AACjB,uBAAe,MAAM,gBAAgB,iCAAiC;AACtE,eAAO,OAAO,iBAAiB,SAAS,cAAc,EAAE,MAAM,KAAK,CAAC;AAAA,MACtE;AACA,UAAI,OAAO,cAAc,UAAa,OAAO,YAAY,GAAG;AAC1D,kBAAU;AAAA,UACR,MAAM,gBAAgB,oCAAoC,OAAO,SAAS,MAAM;AAAA,UAChF,OAAO;AAAA,QACT;AAAA,MACF;AACA,kBAAY,eAAe,MAAM,IAAI,eAAe;AAAA,IACtD,SAAS,OAAO;AACd,sBAAgB,KAAK;AAAA,IACvB;AAAA,EACF,CAAC;AACH;;;AGxEA,IAAM,oBAAoB;AAH1B;AAKO,IAAM,iBAAN,MAAqB;AAAA,EAG1B,YAAY,SAAgC;AAF5C,uBAAS;AAGP,uBAAK,UAAW;AAAA,EAClB;AAAA,EAEA,MAAM,WAAW,MAAoC;AACnD,UAAM,EAAE,QAAQ,iBAAiB,cAAc,QAAQ,UAAU,IAAI,mBAAK;AAC1E,UAAM,WAAW,mBAAK,UAAS,UAAU,KAAK,KAAK,kBAAkB,QAAQ,YAAY,MAAM;AAC/F,uBAAK,UAAS,QAAQ,QAAQ,6BAA6B,QAAQ;AAEnE,UAAM,SAAS,EAAE,UAAU,QAAQ,iBAAiB,cAAc,QAAQ,UAAU;AACpF,WAAO,iBAAiB,MAAM,MAAM;AAAA,EACtC;AACF;AAdW;","names":["SpeechSDK"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/synthesis.ts","../src/speechConfig.ts","../src/outputFormats.ts","../src/client.ts","../src/voiceCatalog.ts"],"sourcesContent":["/**\n * azure-tts-client: Azure Text-to-Speech client for SSML playback.\n */\n\nexport type {\n AzureTtsClientOptions,\n AzureTtsLogger,\n SsmlSynthesisBookmark,\n SsmlSynthesisBoundary,\n SsmlSynthesisResult,\n SsmlSynthesisViseme,\n TtsConfig,\n} from \"./types.ts\";\nexport { AzureTtsError, AzureTtsSdkError } from \"./errors.ts\";\nexport { AzureTtsClient } from \"./client.ts\";\nexport { synthesizeSpeech } from \"./synthesis.ts\";\nexport { synthesizeSsml } from \"./synthesis.ts\";\nexport { fetchAzureVoiceCatalog } from \"./voiceCatalog.ts\";\nexport type {\n AzureVoiceCatalog,\n AzureVoiceCatalogVoice,\n FetchedAzureVoiceCatalogMetadata,\n FetchAzureVoiceCatalogOptions,\n} from \"./voiceCatalog.ts\";\n","export class AzureTtsError extends Error {\n readonly status: number;\n readonly statusText: string;\n readonly responseBody: string;\n readonly requestId: string | null;\n\n constructor(status: number, statusText: string, responseBody: string, requestId: string | null) {\n super(`Azure TTS request failed: ${status} ${statusText}`);\n this.name = \"AzureTtsError\";\n this.status = status;\n this.statusText = statusText;\n this.responseBody = responseBody;\n this.requestId = requestId;\n }\n}\n\nexport class AzureTtsSdkError extends AzureTtsError {\n readonly errorDetails: string;\n\n constructor(errorDetails: string) {\n super(0, \"Speech SDK\", errorDetails, null);\n this.name = \"AzureTtsSdkError\";\n this.message = `Azure TTS synthesis failed: ${errorDetails}`;\n this.errorDetails = errorDetails;\n }\n}\n\nexport function createSpeechSdkError(error: unknown): AzureTtsSdkError {\n const message = error instanceof Error ? error.message : String(error);\n return new AzureTtsSdkError(message);\n}\n","import * as SpeechSDK from \"microsoft-cognitiveservices-speech-sdk\";\nimport { createSpeechSdkError } from \"./errors.ts\";\nimport { createSpeechConfig } from \"./speechConfig.ts\";\nimport type { SsmlSynthesisResult, TtsConfig } from \"./types.ts\";\n\nfunction closeSpeechResources(speechConfig: SpeechSDK.SpeechConfig, synthesizer: SpeechSDK.SpeechSynthesizer): void {\n try {\n synthesizer.close();\n } catch {}\n\n try {\n speechConfig.close();\n } catch {}\n}\n\nconst ticksToMilliseconds = (ticks: number): number => Math.max(0, ticks) / 10_000;\n\nexport async function synthesizeSsml(ssml: string, config: TtsConfig): Promise<SsmlSynthesisResult> {\n if (config.signal?.aborted) {\n throw createSpeechSdkError(\"Speech synthesis was cancelled.\");\n }\n\n const speechConfig = createSpeechConfig(config);\n const synthesizer = new SpeechSDK.SpeechSynthesizer(speechConfig, null);\n\n return await new Promise<SsmlSynthesisResult>((resolve, reject) => {\n let resourcesClosed = false;\n let settled = false;\n let timeout: ReturnType<typeof setTimeout> | undefined;\n let abortHandler: (() => void) | undefined;\n const cleanup = () => {\n if (timeout) clearTimeout(timeout);\n if (abortHandler) config.signal?.removeEventListener(\"abort\", abortHandler);\n };\n const closeResources = () => {\n if (resourcesClosed) return;\n resourcesClosed = true;\n closeSpeechResources(speechConfig, synthesizer);\n };\n const rejectWithError = (error: unknown) => {\n if (settled) return;\n settled = true;\n cleanup();\n closeResources();\n reject(createSpeechSdkError(error));\n };\n\n const boundaries: SsmlSynthesisResult[\"boundaries\"] = [];\n const visemes: SsmlSynthesisResult[\"visemes\"] = [];\n const bookmarks: SsmlSynthesisResult[\"bookmarks\"] = [];\n synthesizer.wordBoundary = (_sender, event) => {\n boundaries.push({\n text: event.text,\n audioOffsetMs: ticksToMilliseconds(event.audioOffset),\n durationMs: ticksToMilliseconds(event.duration),\n });\n };\n synthesizer.visemeReceived = (_sender, event) => {\n visemes.push({ visemeId: event.visemeId, audioOffsetMs: ticksToMilliseconds(event.audioOffset) });\n };\n synthesizer.bookmarkReached = (_sender, event) => {\n bookmarks.push({ name: event.text, audioOffsetMs: ticksToMilliseconds(event.audioOffset) });\n };\n\n const cb = (result: SpeechSDK.SpeechSynthesisResult) => {\n if (settled) return;\n const { reason, errorDetails } = result;\n if (reason !== SpeechSDK.ResultReason.SynthesizingAudioCompleted) {\n const err = errorDetails || `Speech synthesis failed with reason ${reason}.`;\n rejectWithError(err);\n return;\n }\n settled = true;\n cleanup();\n closeResources();\n const eventDurationMs = Math.max(\n 0,\n ...(boundaries ?? []).map((boundary) => boundary.audioOffsetMs + boundary.durationMs),\n ...(visemes ?? []).map((viseme) => viseme.audioOffsetMs),\n ...(bookmarks ?? []).map((bookmark) => bookmark.audioOffsetMs),\n );\n const durationMs = result.audioDuration ? ticksToMilliseconds(result.audioDuration) : eventDurationMs;\n resolve({\n audioData: result.audioData,\n durationMs,\n ...(boundaries.length > 0 ? { boundaries, wordBoundary: boundaries, wordBoundaries: boundaries } : {}),\n ...(visemes.length > 0 ? { visemes } : {}),\n ...(bookmarks.length > 0 ? { bookmarks } : {}),\n });\n };\n\n try {\n if (config.signal) {\n abortHandler = () => rejectWithError(\"Speech synthesis was cancelled.\");\n config.signal.addEventListener(\"abort\", abortHandler, { once: true });\n }\n if (config.timeoutMs !== undefined && config.timeoutMs > 0) {\n timeout = setTimeout(\n () => rejectWithError(`Speech synthesis timed out after ${config.timeoutMs} ms.`),\n config.timeoutMs,\n );\n }\n synthesizer.speakSsmlAsync(ssml, cb, rejectWithError);\n } catch (error) {\n rejectWithError(error);\n }\n });\n}\n\n/** Backward-compatible audio-only synthesis helper. */\nexport async function synthesizeSpeech(ssml: string, config: TtsConfig): Promise<ArrayBuffer> {\n return (await synthesizeSsml(ssml, config)).audioData;\n}\n","import { SpeechConfig } from \"microsoft-cognitiveservices-speech-sdk\";\nimport { DEFAULT_OUTPUT_FORMAT, resolveOutputFormat } from \"./outputFormats.ts\";\nimport type { TtsConfig } from \"./types.ts\";\n\nexport function resolveEndpoint(config: TtsConfig): string {\n const endpoint = config.endpoint?.trim() || \"https://{region}.tts.speech.microsoft.com/cognitiveservices/v1\";\n return endpoint.replace(/\\{region\\}/g, encodeURIComponent(config.region));\n}\n\nexport function createSpeechConfig(config: TtsConfig): SpeechConfig {\n const { outputFormat = DEFAULT_OUTPUT_FORMAT, subscriptionKey } = config;\n\n const endpoint = new URL(resolveEndpoint(config));\n const speechConfig = SpeechConfig.fromEndpoint(endpoint, subscriptionKey);\n speechConfig.speechSynthesisOutputFormat = resolveOutputFormat(outputFormat);\n return speechConfig;\n}\n","import * as SpeechSDK from \"microsoft-cognitiveservices-speech-sdk\";\n\nexport const DEFAULT_OUTPUT_FORMAT = \"audio-16khz-128kbitrate-mono-mp3\";\n\nconst OUTPUT_FORMATS: Record<string, SpeechSDK.SpeechSynthesisOutputFormat> = {\n \"raw-8khz-8bit-mono-mulaw\": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz8BitMonoMULaw,\n \"riff-16khz-16kbps-mono-siren\": SpeechSDK.SpeechSynthesisOutputFormat.Riff16Khz16KbpsMonoSiren,\n \"audio-16khz-16kbps-mono-siren\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz16KbpsMonoSiren,\n \"audio-16khz-32kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz32KBitRateMonoMp3,\n \"audio-16khz-128kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz128KBitRateMonoMp3,\n \"audio-16khz-64kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz64KBitRateMonoMp3,\n \"audio-24khz-48kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz48KBitRateMonoMp3,\n \"audio-24khz-96kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz96KBitRateMonoMp3,\n \"audio-24khz-160kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz160KBitRateMonoMp3,\n \"raw-16khz-16bit-mono-truesilk\": SpeechSDK.SpeechSynthesisOutputFormat.Raw16Khz16BitMonoTrueSilk,\n \"riff-16khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff16Khz16BitMonoPcm,\n \"riff-8khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz16BitMonoPcm,\n \"riff-24khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff24Khz16BitMonoPcm,\n \"riff-8khz-8bit-mono-mulaw\": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz8BitMonoMULaw,\n \"raw-16khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw16Khz16BitMonoPcm,\n \"raw-24khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw24Khz16BitMonoPcm,\n \"raw-8khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz16BitMonoPcm,\n \"ogg-16khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Ogg16Khz16BitMonoOpus,\n \"ogg-24khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Ogg24Khz16BitMonoOpus,\n \"raw-48khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw48Khz16BitMonoPcm,\n \"riff-48khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff48Khz16BitMonoPcm,\n \"audio-48khz-96kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio48Khz96KBitRateMonoMp3,\n \"audio-48khz-192kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio48Khz192KBitRateMonoMp3,\n \"ogg-48khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Ogg48Khz16BitMonoOpus,\n \"webm-16khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Webm16Khz16BitMonoOpus,\n \"webm-24khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Webm24Khz16BitMonoOpus,\n \"webm-24khz-16bit-24kbps-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Webm24Khz16Bit24KbpsMonoOpus,\n \"raw-24khz-16bit-mono-truesilk\": SpeechSDK.SpeechSynthesisOutputFormat.Raw24Khz16BitMonoTrueSilk,\n \"raw-8khz-8bit-mono-alaw\": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz8BitMonoALaw,\n \"riff-8khz-8bit-mono-alaw\": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz8BitMonoALaw,\n \"audio-16khz-16bit-32kbps-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz16Bit32KbpsMonoOpus,\n \"audio-24khz-16bit-48kbps-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz16Bit48KbpsMonoOpus,\n \"audio-24khz-16bit-24kbps-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz16Bit24KbpsMonoOpus,\n \"raw-22050hz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw22050Hz16BitMonoPcm,\n \"riff-22050hz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff22050Hz16BitMonoPcm,\n \"raw-44100hz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw44100Hz16BitMonoPcm,\n \"riff-44100hz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff44100Hz16BitMonoPcm,\n \"amr-wb-16000hz\": SpeechSDK.SpeechSynthesisOutputFormat.AmrWb16000Hz,\n \"g722-16khz-64kbps\": SpeechSDK.SpeechSynthesisOutputFormat.G72216Khz64Kbps,\n};\n\nexport function resolveOutputFormat(outputFormat: string): SpeechSDK.SpeechSynthesisOutputFormat {\n const resolvedFormat = OUTPUT_FORMATS[outputFormat];\n if (resolvedFormat === undefined) {\n throw new Error(`Unsupported Azure Speech output format: ${outputFormat}`);\n }\n\n return resolvedFormat;\n}\n","import { synthesizeSpeech, synthesizeSsml } from \"./synthesis.ts\";\nimport type { AzureTtsClientOptions, SsmlSynthesisResult } from \"./types.ts\";\n\nconst ENDPOINT_TEMPLATE = \"https://{region}.tts.speech.microsoft.com/cognitiveservices/v1\";\n\nexport class AzureTtsClient {\n readonly #options: AzureTtsClientOptions;\n\n constructor(options: AzureTtsClientOptions) {\n this.#options = options;\n }\n\n async synthesize(ssml: string): Promise<ArrayBuffer> {\n const { region, subscriptionKey, outputFormat, signal, timeoutMs } = this.#options;\n const endpoint = this.#options.endpoint?.trim() || ENDPOINT_TEMPLATE.replace(\"{region}\", region);\n this.#options.logger?.debug?.(\"Using Azure TTS endpoint:\", endpoint);\n\n const config = { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs };\n return synthesizeSpeech(ssml, config);\n }\n\n async synthesizeSsml(ssml: string): Promise<SsmlSynthesisResult> {\n const { region, subscriptionKey, outputFormat, signal, timeoutMs } = this.#options;\n const endpoint = this.#options.endpoint?.trim() || ENDPOINT_TEMPLATE.replace(\"{region}\", region);\n this.#options.logger?.debug?.(\"Using Azure TTS endpoint:\", endpoint);\n\n return synthesizeSsml(ssml, { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs });\n }\n}\n","const AZURE_VOICE_API_VERSION = \"2025-10-01\";\n\nexport interface FetchAzureVoiceCatalogOptions {\n apiKey: string;\n region: string | string[];\n}\n\nexport interface AzureVoiceCatalogVoice {\n name: string;\n locale: string;\n secondaryLocales?: readonly string[];\n styles?: readonly string[];\n regions: readonly string[];\n status?: \"ga\" | \"preview\" | \"deprecated\";\n}\n\nexport interface FetchedAzureVoiceCatalogMetadata {\n voiceCount: number;\n generatedAt: string;\n apiVersion: string;\n regions: readonly string[];\n}\n\nexport interface AzureVoiceCatalog {\n voices: readonly AzureVoiceCatalogVoice[];\n metadata: FetchedAzureVoiceCatalogMetadata;\n}\n\ninterface AzureVoiceApiRecord {\n Locale?: unknown;\n Name?: unknown;\n SecondaryLocaleList?: unknown;\n ShortName?: unknown;\n Status?: unknown;\n StyleList?: unknown;\n}\n\nfunction stringValue(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim() ? value.trim() : undefined;\n}\n\nfunction stringList(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return [...new Set(value.map(stringValue).filter((item): item is string => item !== undefined))];\n}\n\nfunction normalizeStatus(value: unknown): AzureVoiceCatalogVoice[\"status\"] {\n const status = stringValue(value)?.toLowerCase();\n if (status === \"preview\" || status === \"deprecated\" || status === \"ga\") return status;\n return undefined;\n}\n\nfunction normalizeRegions(region: string | string[]): string[] {\n const regions = Array.isArray(region) ? region : [region];\n const result = [...new Set(regions.map((item) => item.trim()).filter(Boolean))];\n if (result.length === 0) throw new TypeError(\"At least one Azure Speech region is required.\");\n return result;\n}\n\nasync function fetchRegionVoices(region: string, apiKey: string): Promise<AzureVoiceApiRecord[]> {\n const endpoint = `https://${encodeURIComponent(region)}.tts.speech.microsoft.com/cognitiveservices/voices/list`;\n const response = await fetch(endpoint, {\n headers: {\n Accept: \"application/json\",\n \"Ocp-Apim-Subscription-Key\": apiKey,\n },\n });\n if (!response.ok) {\n throw new Error(`Azure List Voices API request failed for region \"${region}\" with HTTP ${response.status}.`);\n }\n const payload: unknown = await response.json();\n if (!Array.isArray(payload)) throw new Error(`Azure List Voices API returned an invalid response for \"${region}\".`);\n return payload.filter((item): item is AzureVoiceApiRecord => Boolean(item && typeof item === \"object\"));\n}\n\n/** Fetches and deduplicates the current Azure Speech voice catalog for one or more regions. */\nexport async function fetchAzureVoiceCatalog(options: FetchAzureVoiceCatalogOptions): Promise<AzureVoiceCatalog> {\n if (!options || typeof options.apiKey !== \"string\" || !options.apiKey.trim())\n throw new TypeError(\"An Azure Speech API key is required.\");\n const regions = normalizeRegions(options.region);\n const payloads = await Promise.all(regions.map((region) => fetchRegionVoices(region, options.apiKey)));\n const voices = new Map<string, AzureVoiceCatalogVoice>();\n\n for (let regionIndex = 0; regionIndex < payloads.length; regionIndex += 1) {\n const region = regions[regionIndex];\n for (const record of payloads[regionIndex]) {\n const name = stringValue(record.ShortName) ?? stringValue(record.Name);\n const locale = stringValue(record.Locale);\n if (!name || !locale) continue;\n const key = name.toLowerCase();\n const existing = voices.get(key);\n const secondaryLocales = stringList(record.SecondaryLocaleList);\n const styles = stringList(record.StyleList);\n const status = normalizeStatus(record.Status);\n const merged: AzureVoiceCatalogVoice = {\n name: existing?.name ?? name,\n locale: existing?.locale ?? locale,\n regions: [...new Set([...(existing?.regions ?? []), region])],\n };\n const mergedSecondaryLocales = [...new Set([...(existing?.secondaryLocales ?? []), ...secondaryLocales])];\n if (mergedSecondaryLocales.length > 0) merged.secondaryLocales = mergedSecondaryLocales;\n const mergedStyles = [...new Set([...(existing?.styles ?? []), ...styles])];\n if (mergedStyles.length > 0) merged.styles = mergedStyles;\n if (status) merged.status = status;\n else if (existing?.status) merged.status = existing.status;\n voices.set(key, merged);\n }\n }\n\n const sortedVoices = [...voices.values()].sort((first, second) => first.name.localeCompare(second.name));\n return {\n voices: sortedVoices,\n metadata: {\n voiceCount: sortedVoices.length,\n generatedAt: new Date().toISOString(),\n apiVersion: AZURE_VOICE_API_VERSION,\n regions,\n },\n };\n}\n"],"mappings":"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","names":["SpeechSDK"]}
|
package/dist/index.mjs
CHANGED
|
@@ -112,7 +112,8 @@ function closeSpeechResources(speechConfig, synthesizer) {
|
|
|
112
112
|
} catch {
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
|
-
|
|
115
|
+
var ticksToMilliseconds = (ticks) => Math.max(0, ticks) / 1e4;
|
|
116
|
+
async function synthesizeSsml(ssml, config) {
|
|
116
117
|
if (config.signal?.aborted) {
|
|
117
118
|
throw createSpeechSdkError("Speech synthesis was cancelled.");
|
|
118
119
|
}
|
|
@@ -139,6 +140,22 @@ async function synthesizeSpeech(ssml, config) {
|
|
|
139
140
|
closeResources();
|
|
140
141
|
reject(createSpeechSdkError(error));
|
|
141
142
|
};
|
|
143
|
+
const boundaries = [];
|
|
144
|
+
const visemes = [];
|
|
145
|
+
const bookmarks = [];
|
|
146
|
+
synthesizer.wordBoundary = (_sender, event) => {
|
|
147
|
+
boundaries.push({
|
|
148
|
+
text: event.text,
|
|
149
|
+
audioOffsetMs: ticksToMilliseconds(event.audioOffset),
|
|
150
|
+
durationMs: ticksToMilliseconds(event.duration)
|
|
151
|
+
});
|
|
152
|
+
};
|
|
153
|
+
synthesizer.visemeReceived = (_sender, event) => {
|
|
154
|
+
visemes.push({ visemeId: event.visemeId, audioOffsetMs: ticksToMilliseconds(event.audioOffset) });
|
|
155
|
+
};
|
|
156
|
+
synthesizer.bookmarkReached = (_sender, event) => {
|
|
157
|
+
bookmarks.push({ name: event.text, audioOffsetMs: ticksToMilliseconds(event.audioOffset) });
|
|
158
|
+
};
|
|
142
159
|
const cb = (result) => {
|
|
143
160
|
if (settled) return;
|
|
144
161
|
const { reason, errorDetails } = result;
|
|
@@ -150,7 +167,20 @@ async function synthesizeSpeech(ssml, config) {
|
|
|
150
167
|
settled = true;
|
|
151
168
|
cleanup();
|
|
152
169
|
closeResources();
|
|
153
|
-
|
|
170
|
+
const eventDurationMs = Math.max(
|
|
171
|
+
0,
|
|
172
|
+
...(boundaries ?? []).map((boundary) => boundary.audioOffsetMs + boundary.durationMs),
|
|
173
|
+
...(visemes ?? []).map((viseme) => viseme.audioOffsetMs),
|
|
174
|
+
...(bookmarks ?? []).map((bookmark) => bookmark.audioOffsetMs)
|
|
175
|
+
);
|
|
176
|
+
const durationMs = result.audioDuration ? ticksToMilliseconds(result.audioDuration) : eventDurationMs;
|
|
177
|
+
resolve({
|
|
178
|
+
audioData: result.audioData,
|
|
179
|
+
durationMs,
|
|
180
|
+
...boundaries.length > 0 ? { boundaries, wordBoundary: boundaries, wordBoundaries: boundaries } : {},
|
|
181
|
+
...visemes.length > 0 ? { visemes } : {},
|
|
182
|
+
...bookmarks.length > 0 ? { bookmarks } : {}
|
|
183
|
+
});
|
|
154
184
|
};
|
|
155
185
|
try {
|
|
156
186
|
if (config.signal) {
|
|
@@ -169,6 +199,9 @@ async function synthesizeSpeech(ssml, config) {
|
|
|
169
199
|
}
|
|
170
200
|
});
|
|
171
201
|
}
|
|
202
|
+
async function synthesizeSpeech(ssml, config) {
|
|
203
|
+
return (await synthesizeSsml(ssml, config)).audioData;
|
|
204
|
+
}
|
|
172
205
|
|
|
173
206
|
// src/client.ts
|
|
174
207
|
var ENDPOINT_TEMPLATE = "https://{region}.tts.speech.microsoft.com/cognitiveservices/v1";
|
|
@@ -185,12 +218,98 @@ var AzureTtsClient = class {
|
|
|
185
218
|
const config = { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs };
|
|
186
219
|
return synthesizeSpeech(ssml, config);
|
|
187
220
|
}
|
|
221
|
+
async synthesizeSsml(ssml) {
|
|
222
|
+
const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
|
|
223
|
+
const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
224
|
+
__privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
|
|
225
|
+
return synthesizeSsml(ssml, { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs });
|
|
226
|
+
}
|
|
188
227
|
};
|
|
189
228
|
_options = new WeakMap();
|
|
229
|
+
|
|
230
|
+
// src/voiceCatalog.ts
|
|
231
|
+
var AZURE_VOICE_API_VERSION = "2025-10-01";
|
|
232
|
+
function stringValue(value) {
|
|
233
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
234
|
+
}
|
|
235
|
+
function stringList(value) {
|
|
236
|
+
if (!Array.isArray(value)) return [];
|
|
237
|
+
return [...new Set(value.map(stringValue).filter((item) => item !== void 0))];
|
|
238
|
+
}
|
|
239
|
+
function normalizeStatus(value) {
|
|
240
|
+
const status = stringValue(value)?.toLowerCase();
|
|
241
|
+
if (status === "preview" || status === "deprecated" || status === "ga") return status;
|
|
242
|
+
return void 0;
|
|
243
|
+
}
|
|
244
|
+
function normalizeRegions(region) {
|
|
245
|
+
const regions = Array.isArray(region) ? region : [region];
|
|
246
|
+
const result = [...new Set(regions.map((item) => item.trim()).filter(Boolean))];
|
|
247
|
+
if (result.length === 0) throw new TypeError("At least one Azure Speech region is required.");
|
|
248
|
+
return result;
|
|
249
|
+
}
|
|
250
|
+
async function fetchRegionVoices(region, apiKey) {
|
|
251
|
+
const endpoint = `https://${encodeURIComponent(region)}.tts.speech.microsoft.com/cognitiveservices/voices/list`;
|
|
252
|
+
const response = await fetch(endpoint, {
|
|
253
|
+
headers: {
|
|
254
|
+
Accept: "application/json",
|
|
255
|
+
"Ocp-Apim-Subscription-Key": apiKey
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
if (!response.ok) {
|
|
259
|
+
throw new Error(`Azure List Voices API request failed for region "${region}" with HTTP ${response.status}.`);
|
|
260
|
+
}
|
|
261
|
+
const payload = await response.json();
|
|
262
|
+
if (!Array.isArray(payload)) throw new Error(`Azure List Voices API returned an invalid response for "${region}".`);
|
|
263
|
+
return payload.filter((item) => Boolean(item && typeof item === "object"));
|
|
264
|
+
}
|
|
265
|
+
async function fetchAzureVoiceCatalog(options) {
|
|
266
|
+
if (!options || typeof options.apiKey !== "string" || !options.apiKey.trim())
|
|
267
|
+
throw new TypeError("An Azure Speech API key is required.");
|
|
268
|
+
const regions = normalizeRegions(options.region);
|
|
269
|
+
const payloads = await Promise.all(regions.map((region) => fetchRegionVoices(region, options.apiKey)));
|
|
270
|
+
const voices = /* @__PURE__ */ new Map();
|
|
271
|
+
for (let regionIndex = 0; regionIndex < payloads.length; regionIndex += 1) {
|
|
272
|
+
const region = regions[regionIndex];
|
|
273
|
+
for (const record of payloads[regionIndex]) {
|
|
274
|
+
const name = stringValue(record.ShortName) ?? stringValue(record.Name);
|
|
275
|
+
const locale = stringValue(record.Locale);
|
|
276
|
+
if (!name || !locale) continue;
|
|
277
|
+
const key = name.toLowerCase();
|
|
278
|
+
const existing = voices.get(key);
|
|
279
|
+
const secondaryLocales = stringList(record.SecondaryLocaleList);
|
|
280
|
+
const styles = stringList(record.StyleList);
|
|
281
|
+
const status = normalizeStatus(record.Status);
|
|
282
|
+
const merged = {
|
|
283
|
+
name: existing?.name ?? name,
|
|
284
|
+
locale: existing?.locale ?? locale,
|
|
285
|
+
regions: [.../* @__PURE__ */ new Set([...existing?.regions ?? [], region])]
|
|
286
|
+
};
|
|
287
|
+
const mergedSecondaryLocales = [.../* @__PURE__ */ new Set([...existing?.secondaryLocales ?? [], ...secondaryLocales])];
|
|
288
|
+
if (mergedSecondaryLocales.length > 0) merged.secondaryLocales = mergedSecondaryLocales;
|
|
289
|
+
const mergedStyles = [.../* @__PURE__ */ new Set([...existing?.styles ?? [], ...styles])];
|
|
290
|
+
if (mergedStyles.length > 0) merged.styles = mergedStyles;
|
|
291
|
+
if (status) merged.status = status;
|
|
292
|
+
else if (existing?.status) merged.status = existing.status;
|
|
293
|
+
voices.set(key, merged);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
const sortedVoices = [...voices.values()].sort((first, second) => first.name.localeCompare(second.name));
|
|
297
|
+
return {
|
|
298
|
+
voices: sortedVoices,
|
|
299
|
+
metadata: {
|
|
300
|
+
voiceCount: sortedVoices.length,
|
|
301
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
302
|
+
apiVersion: AZURE_VOICE_API_VERSION,
|
|
303
|
+
regions
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
}
|
|
190
307
|
export {
|
|
191
308
|
AzureTtsClient,
|
|
192
309
|
AzureTtsError,
|
|
193
310
|
AzureTtsSdkError,
|
|
194
|
-
|
|
311
|
+
fetchAzureVoiceCatalog,
|
|
312
|
+
synthesizeSpeech,
|
|
313
|
+
synthesizeSsml
|
|
195
314
|
};
|
|
196
315
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/synthesis.ts","../src/speechConfig.ts","../src/outputFormats.ts","../src/client.ts"],"sourcesContent":["export class AzureTtsError extends Error {\n readonly status: number;\n readonly statusText: string;\n readonly responseBody: string;\n readonly requestId: string | null;\n\n constructor(status: number, statusText: string, responseBody: string, requestId: string | null) {\n super(`Azure TTS request failed: ${status} ${statusText}`);\n this.name = \"AzureTtsError\";\n this.status = status;\n this.statusText = statusText;\n this.responseBody = responseBody;\n this.requestId = requestId;\n }\n}\n\nexport class AzureTtsSdkError extends AzureTtsError {\n readonly errorDetails: string;\n\n constructor(errorDetails: string) {\n super(0, \"Speech SDK\", errorDetails, null);\n this.name = \"AzureTtsSdkError\";\n this.message = `Azure TTS synthesis failed: ${errorDetails}`;\n this.errorDetails = errorDetails;\n }\n}\n\nexport function createSpeechSdkError(error: unknown): AzureTtsSdkError {\n const message = error instanceof Error ? error.message : String(error);\n return new AzureTtsSdkError(message);\n}\n","import * as SpeechSDK from \"microsoft-cognitiveservices-speech-sdk\";\nimport { createSpeechSdkError } from \"./errors.ts\";\nimport { createSpeechConfig } from \"./speechConfig.ts\";\nimport type { TtsConfig } from \"./types.ts\";\n\nfunction closeSpeechResources(speechConfig: SpeechSDK.SpeechConfig, synthesizer: SpeechSDK.SpeechSynthesizer): void {\n try {\n synthesizer.close();\n } catch {}\n\n try {\n speechConfig.close();\n } catch {}\n}\n\nexport async function synthesizeSpeech(ssml: string, config: TtsConfig): Promise<ArrayBuffer> {\n if (config.signal?.aborted) {\n throw createSpeechSdkError(\"Speech synthesis was cancelled.\");\n }\n\n const speechConfig = createSpeechConfig(config);\n const synthesizer = new SpeechSDK.SpeechSynthesizer(speechConfig, null);\n\n return await new Promise<ArrayBuffer>((resolve, reject) => {\n let resourcesClosed = false;\n let settled = false;\n let timeout: ReturnType<typeof setTimeout> | undefined;\n let abortHandler: (() => void) | undefined;\n const cleanup = () => {\n if (timeout) clearTimeout(timeout);\n if (abortHandler) config.signal?.removeEventListener(\"abort\", abortHandler);\n };\n const closeResources = () => {\n if (resourcesClosed) return;\n resourcesClosed = true;\n closeSpeechResources(speechConfig, synthesizer);\n };\n const rejectWithError = (error: unknown) => {\n if (settled) return;\n settled = true;\n cleanup();\n closeResources();\n reject(createSpeechSdkError(error));\n };\n\n const cb = (result: SpeechSDK.SpeechSynthesisResult) => {\n if (settled) return;\n const { reason, errorDetails } = result;\n if (reason !== SpeechSDK.ResultReason.SynthesizingAudioCompleted) {\n const err = errorDetails || `Speech synthesis failed with reason ${reason}.`;\n rejectWithError(err);\n return;\n }\n settled = true;\n cleanup();\n closeResources();\n resolve(result.audioData);\n };\n\n try {\n if (config.signal) {\n abortHandler = () => rejectWithError(\"Speech synthesis was cancelled.\");\n config.signal.addEventListener(\"abort\", abortHandler, { once: true });\n }\n if (config.timeoutMs !== undefined && config.timeoutMs > 0) {\n timeout = setTimeout(\n () => rejectWithError(`Speech synthesis timed out after ${config.timeoutMs} ms.`),\n config.timeoutMs,\n );\n }\n synthesizer.speakSsmlAsync(ssml, cb, rejectWithError);\n } catch (error) {\n rejectWithError(error);\n }\n });\n}\n","import { SpeechConfig } from \"microsoft-cognitiveservices-speech-sdk\";\nimport { DEFAULT_OUTPUT_FORMAT, resolveOutputFormat } from \"./outputFormats.ts\";\nimport type { TtsConfig } from \"./types.ts\";\n\nexport function resolveEndpoint(config: TtsConfig): string {\n const endpoint = config.endpoint?.trim() || \"https://{region}.tts.speech.microsoft.com/cognitiveservices/v1\";\n return endpoint.replace(/\\{region\\}/g, encodeURIComponent(config.region));\n}\n\nexport function createSpeechConfig(config: TtsConfig): SpeechConfig {\n const { outputFormat = DEFAULT_OUTPUT_FORMAT, subscriptionKey } = config;\n\n const endpoint = new URL(resolveEndpoint(config));\n const speechConfig = SpeechConfig.fromEndpoint(endpoint, subscriptionKey);\n speechConfig.speechSynthesisOutputFormat = resolveOutputFormat(outputFormat);\n return speechConfig;\n}\n","import * as SpeechSDK from \"microsoft-cognitiveservices-speech-sdk\";\n\nexport const DEFAULT_OUTPUT_FORMAT = \"audio-16khz-128kbitrate-mono-mp3\";\n\nconst OUTPUT_FORMATS: Record<string, SpeechSDK.SpeechSynthesisOutputFormat> = {\n \"raw-8khz-8bit-mono-mulaw\": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz8BitMonoMULaw,\n \"riff-16khz-16kbps-mono-siren\": SpeechSDK.SpeechSynthesisOutputFormat.Riff16Khz16KbpsMonoSiren,\n \"audio-16khz-16kbps-mono-siren\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz16KbpsMonoSiren,\n \"audio-16khz-32kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz32KBitRateMonoMp3,\n \"audio-16khz-128kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz128KBitRateMonoMp3,\n \"audio-16khz-64kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz64KBitRateMonoMp3,\n \"audio-24khz-48kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz48KBitRateMonoMp3,\n \"audio-24khz-96kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz96KBitRateMonoMp3,\n \"audio-24khz-160kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz160KBitRateMonoMp3,\n \"raw-16khz-16bit-mono-truesilk\": SpeechSDK.SpeechSynthesisOutputFormat.Raw16Khz16BitMonoTrueSilk,\n \"riff-16khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff16Khz16BitMonoPcm,\n \"riff-8khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz16BitMonoPcm,\n \"riff-24khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff24Khz16BitMonoPcm,\n \"riff-8khz-8bit-mono-mulaw\": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz8BitMonoMULaw,\n \"raw-16khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw16Khz16BitMonoPcm,\n \"raw-24khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw24Khz16BitMonoPcm,\n \"raw-8khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz16BitMonoPcm,\n \"ogg-16khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Ogg16Khz16BitMonoOpus,\n \"ogg-24khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Ogg24Khz16BitMonoOpus,\n \"raw-48khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw48Khz16BitMonoPcm,\n \"riff-48khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff48Khz16BitMonoPcm,\n \"audio-48khz-96kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio48Khz96KBitRateMonoMp3,\n \"audio-48khz-192kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio48Khz192KBitRateMonoMp3,\n \"ogg-48khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Ogg48Khz16BitMonoOpus,\n \"webm-16khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Webm16Khz16BitMonoOpus,\n \"webm-24khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Webm24Khz16BitMonoOpus,\n \"webm-24khz-16bit-24kbps-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Webm24Khz16Bit24KbpsMonoOpus,\n \"raw-24khz-16bit-mono-truesilk\": SpeechSDK.SpeechSynthesisOutputFormat.Raw24Khz16BitMonoTrueSilk,\n \"raw-8khz-8bit-mono-alaw\": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz8BitMonoALaw,\n \"riff-8khz-8bit-mono-alaw\": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz8BitMonoALaw,\n \"audio-16khz-16bit-32kbps-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz16Bit32KbpsMonoOpus,\n \"audio-24khz-16bit-48kbps-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz16Bit48KbpsMonoOpus,\n \"audio-24khz-16bit-24kbps-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz16Bit24KbpsMonoOpus,\n \"raw-22050hz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw22050Hz16BitMonoPcm,\n \"riff-22050hz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff22050Hz16BitMonoPcm,\n \"raw-44100hz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw44100Hz16BitMonoPcm,\n \"riff-44100hz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff44100Hz16BitMonoPcm,\n \"amr-wb-16000hz\": SpeechSDK.SpeechSynthesisOutputFormat.AmrWb16000Hz,\n \"g722-16khz-64kbps\": SpeechSDK.SpeechSynthesisOutputFormat.G72216Khz64Kbps,\n};\n\nexport function resolveOutputFormat(outputFormat: string): SpeechSDK.SpeechSynthesisOutputFormat {\n const resolvedFormat = OUTPUT_FORMATS[outputFormat];\n if (resolvedFormat === undefined) {\n throw new Error(`Unsupported Azure Speech output format: ${outputFormat}`);\n }\n\n return resolvedFormat;\n}\n","import { synthesizeSpeech } from \"./synthesis.ts\";\nimport type { AzureTtsClientOptions } from \"./types.ts\";\n\nconst ENDPOINT_TEMPLATE = \"https://{region}.tts.speech.microsoft.com/cognitiveservices/v1\";\n\nexport class AzureTtsClient {\n readonly #options: AzureTtsClientOptions;\n\n constructor(options: AzureTtsClientOptions) {\n this.#options = options;\n }\n\n async synthesize(ssml: string): Promise<ArrayBuffer> {\n const { region, subscriptionKey, outputFormat, signal, timeoutMs } = this.#options;\n const endpoint = this.#options.endpoint?.trim() || ENDPOINT_TEMPLATE.replace(\"{region}\", region);\n this.#options.logger?.debug?.(\"Using Azure TTS endpoint:\", endpoint);\n\n const config = { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs };\n return synthesizeSpeech(ssml, config);\n }\n}\n"],"mappings":"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","names":["SpeechSDK"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/synthesis.ts","../src/speechConfig.ts","../src/outputFormats.ts","../src/client.ts","../src/voiceCatalog.ts"],"sourcesContent":["export class AzureTtsError extends Error {\n readonly status: number;\n readonly statusText: string;\n readonly responseBody: string;\n readonly requestId: string | null;\n\n constructor(status: number, statusText: string, responseBody: string, requestId: string | null) {\n super(`Azure TTS request failed: ${status} ${statusText}`);\n this.name = \"AzureTtsError\";\n this.status = status;\n this.statusText = statusText;\n this.responseBody = responseBody;\n this.requestId = requestId;\n }\n}\n\nexport class AzureTtsSdkError extends AzureTtsError {\n readonly errorDetails: string;\n\n constructor(errorDetails: string) {\n super(0, \"Speech SDK\", errorDetails, null);\n this.name = \"AzureTtsSdkError\";\n this.message = `Azure TTS synthesis failed: ${errorDetails}`;\n this.errorDetails = errorDetails;\n }\n}\n\nexport function createSpeechSdkError(error: unknown): AzureTtsSdkError {\n const message = error instanceof Error ? error.message : String(error);\n return new AzureTtsSdkError(message);\n}\n","import * as SpeechSDK from \"microsoft-cognitiveservices-speech-sdk\";\nimport { createSpeechSdkError } from \"./errors.ts\";\nimport { createSpeechConfig } from \"./speechConfig.ts\";\nimport type { SsmlSynthesisResult, TtsConfig } from \"./types.ts\";\n\nfunction closeSpeechResources(speechConfig: SpeechSDK.SpeechConfig, synthesizer: SpeechSDK.SpeechSynthesizer): void {\n try {\n synthesizer.close();\n } catch {}\n\n try {\n speechConfig.close();\n } catch {}\n}\n\nconst ticksToMilliseconds = (ticks: number): number => Math.max(0, ticks) / 10_000;\n\nexport async function synthesizeSsml(ssml: string, config: TtsConfig): Promise<SsmlSynthesisResult> {\n if (config.signal?.aborted) {\n throw createSpeechSdkError(\"Speech synthesis was cancelled.\");\n }\n\n const speechConfig = createSpeechConfig(config);\n const synthesizer = new SpeechSDK.SpeechSynthesizer(speechConfig, null);\n\n return await new Promise<SsmlSynthesisResult>((resolve, reject) => {\n let resourcesClosed = false;\n let settled = false;\n let timeout: ReturnType<typeof setTimeout> | undefined;\n let abortHandler: (() => void) | undefined;\n const cleanup = () => {\n if (timeout) clearTimeout(timeout);\n if (abortHandler) config.signal?.removeEventListener(\"abort\", abortHandler);\n };\n const closeResources = () => {\n if (resourcesClosed) return;\n resourcesClosed = true;\n closeSpeechResources(speechConfig, synthesizer);\n };\n const rejectWithError = (error: unknown) => {\n if (settled) return;\n settled = true;\n cleanup();\n closeResources();\n reject(createSpeechSdkError(error));\n };\n\n const boundaries: SsmlSynthesisResult[\"boundaries\"] = [];\n const visemes: SsmlSynthesisResult[\"visemes\"] = [];\n const bookmarks: SsmlSynthesisResult[\"bookmarks\"] = [];\n synthesizer.wordBoundary = (_sender, event) => {\n boundaries.push({\n text: event.text,\n audioOffsetMs: ticksToMilliseconds(event.audioOffset),\n durationMs: ticksToMilliseconds(event.duration),\n });\n };\n synthesizer.visemeReceived = (_sender, event) => {\n visemes.push({ visemeId: event.visemeId, audioOffsetMs: ticksToMilliseconds(event.audioOffset) });\n };\n synthesizer.bookmarkReached = (_sender, event) => {\n bookmarks.push({ name: event.text, audioOffsetMs: ticksToMilliseconds(event.audioOffset) });\n };\n\n const cb = (result: SpeechSDK.SpeechSynthesisResult) => {\n if (settled) return;\n const { reason, errorDetails } = result;\n if (reason !== SpeechSDK.ResultReason.SynthesizingAudioCompleted) {\n const err = errorDetails || `Speech synthesis failed with reason ${reason}.`;\n rejectWithError(err);\n return;\n }\n settled = true;\n cleanup();\n closeResources();\n const eventDurationMs = Math.max(\n 0,\n ...(boundaries ?? []).map((boundary) => boundary.audioOffsetMs + boundary.durationMs),\n ...(visemes ?? []).map((viseme) => viseme.audioOffsetMs),\n ...(bookmarks ?? []).map((bookmark) => bookmark.audioOffsetMs),\n );\n const durationMs = result.audioDuration ? ticksToMilliseconds(result.audioDuration) : eventDurationMs;\n resolve({\n audioData: result.audioData,\n durationMs,\n ...(boundaries.length > 0 ? { boundaries, wordBoundary: boundaries, wordBoundaries: boundaries } : {}),\n ...(visemes.length > 0 ? { visemes } : {}),\n ...(bookmarks.length > 0 ? { bookmarks } : {}),\n });\n };\n\n try {\n if (config.signal) {\n abortHandler = () => rejectWithError(\"Speech synthesis was cancelled.\");\n config.signal.addEventListener(\"abort\", abortHandler, { once: true });\n }\n if (config.timeoutMs !== undefined && config.timeoutMs > 0) {\n timeout = setTimeout(\n () => rejectWithError(`Speech synthesis timed out after ${config.timeoutMs} ms.`),\n config.timeoutMs,\n );\n }\n synthesizer.speakSsmlAsync(ssml, cb, rejectWithError);\n } catch (error) {\n rejectWithError(error);\n }\n });\n}\n\n/** Backward-compatible audio-only synthesis helper. */\nexport async function synthesizeSpeech(ssml: string, config: TtsConfig): Promise<ArrayBuffer> {\n return (await synthesizeSsml(ssml, config)).audioData;\n}\n","import { SpeechConfig } from \"microsoft-cognitiveservices-speech-sdk\";\nimport { DEFAULT_OUTPUT_FORMAT, resolveOutputFormat } from \"./outputFormats.ts\";\nimport type { TtsConfig } from \"./types.ts\";\n\nexport function resolveEndpoint(config: TtsConfig): string {\n const endpoint = config.endpoint?.trim() || \"https://{region}.tts.speech.microsoft.com/cognitiveservices/v1\";\n return endpoint.replace(/\\{region\\}/g, encodeURIComponent(config.region));\n}\n\nexport function createSpeechConfig(config: TtsConfig): SpeechConfig {\n const { outputFormat = DEFAULT_OUTPUT_FORMAT, subscriptionKey } = config;\n\n const endpoint = new URL(resolveEndpoint(config));\n const speechConfig = SpeechConfig.fromEndpoint(endpoint, subscriptionKey);\n speechConfig.speechSynthesisOutputFormat = resolveOutputFormat(outputFormat);\n return speechConfig;\n}\n","import * as SpeechSDK from \"microsoft-cognitiveservices-speech-sdk\";\n\nexport const DEFAULT_OUTPUT_FORMAT = \"audio-16khz-128kbitrate-mono-mp3\";\n\nconst OUTPUT_FORMATS: Record<string, SpeechSDK.SpeechSynthesisOutputFormat> = {\n \"raw-8khz-8bit-mono-mulaw\": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz8BitMonoMULaw,\n \"riff-16khz-16kbps-mono-siren\": SpeechSDK.SpeechSynthesisOutputFormat.Riff16Khz16KbpsMonoSiren,\n \"audio-16khz-16kbps-mono-siren\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz16KbpsMonoSiren,\n \"audio-16khz-32kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz32KBitRateMonoMp3,\n \"audio-16khz-128kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz128KBitRateMonoMp3,\n \"audio-16khz-64kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz64KBitRateMonoMp3,\n \"audio-24khz-48kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz48KBitRateMonoMp3,\n \"audio-24khz-96kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz96KBitRateMonoMp3,\n \"audio-24khz-160kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz160KBitRateMonoMp3,\n \"raw-16khz-16bit-mono-truesilk\": SpeechSDK.SpeechSynthesisOutputFormat.Raw16Khz16BitMonoTrueSilk,\n \"riff-16khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff16Khz16BitMonoPcm,\n \"riff-8khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz16BitMonoPcm,\n \"riff-24khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff24Khz16BitMonoPcm,\n \"riff-8khz-8bit-mono-mulaw\": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz8BitMonoMULaw,\n \"raw-16khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw16Khz16BitMonoPcm,\n \"raw-24khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw24Khz16BitMonoPcm,\n \"raw-8khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz16BitMonoPcm,\n \"ogg-16khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Ogg16Khz16BitMonoOpus,\n \"ogg-24khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Ogg24Khz16BitMonoOpus,\n \"raw-48khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw48Khz16BitMonoPcm,\n \"riff-48khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff48Khz16BitMonoPcm,\n \"audio-48khz-96kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio48Khz96KBitRateMonoMp3,\n \"audio-48khz-192kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio48Khz192KBitRateMonoMp3,\n \"ogg-48khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Ogg48Khz16BitMonoOpus,\n \"webm-16khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Webm16Khz16BitMonoOpus,\n \"webm-24khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Webm24Khz16BitMonoOpus,\n \"webm-24khz-16bit-24kbps-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Webm24Khz16Bit24KbpsMonoOpus,\n \"raw-24khz-16bit-mono-truesilk\": SpeechSDK.SpeechSynthesisOutputFormat.Raw24Khz16BitMonoTrueSilk,\n \"raw-8khz-8bit-mono-alaw\": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz8BitMonoALaw,\n \"riff-8khz-8bit-mono-alaw\": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz8BitMonoALaw,\n \"audio-16khz-16bit-32kbps-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz16Bit32KbpsMonoOpus,\n \"audio-24khz-16bit-48kbps-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz16Bit48KbpsMonoOpus,\n \"audio-24khz-16bit-24kbps-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz16Bit24KbpsMonoOpus,\n \"raw-22050hz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw22050Hz16BitMonoPcm,\n \"riff-22050hz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff22050Hz16BitMonoPcm,\n \"raw-44100hz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw44100Hz16BitMonoPcm,\n \"riff-44100hz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff44100Hz16BitMonoPcm,\n \"amr-wb-16000hz\": SpeechSDK.SpeechSynthesisOutputFormat.AmrWb16000Hz,\n \"g722-16khz-64kbps\": SpeechSDK.SpeechSynthesisOutputFormat.G72216Khz64Kbps,\n};\n\nexport function resolveOutputFormat(outputFormat: string): SpeechSDK.SpeechSynthesisOutputFormat {\n const resolvedFormat = OUTPUT_FORMATS[outputFormat];\n if (resolvedFormat === undefined) {\n throw new Error(`Unsupported Azure Speech output format: ${outputFormat}`);\n }\n\n return resolvedFormat;\n}\n","import { synthesizeSpeech, synthesizeSsml } from \"./synthesis.ts\";\nimport type { AzureTtsClientOptions, SsmlSynthesisResult } from \"./types.ts\";\n\nconst ENDPOINT_TEMPLATE = \"https://{region}.tts.speech.microsoft.com/cognitiveservices/v1\";\n\nexport class AzureTtsClient {\n readonly #options: AzureTtsClientOptions;\n\n constructor(options: AzureTtsClientOptions) {\n this.#options = options;\n }\n\n async synthesize(ssml: string): Promise<ArrayBuffer> {\n const { region, subscriptionKey, outputFormat, signal, timeoutMs } = this.#options;\n const endpoint = this.#options.endpoint?.trim() || ENDPOINT_TEMPLATE.replace(\"{region}\", region);\n this.#options.logger?.debug?.(\"Using Azure TTS endpoint:\", endpoint);\n\n const config = { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs };\n return synthesizeSpeech(ssml, config);\n }\n\n async synthesizeSsml(ssml: string): Promise<SsmlSynthesisResult> {\n const { region, subscriptionKey, outputFormat, signal, timeoutMs } = this.#options;\n const endpoint = this.#options.endpoint?.trim() || ENDPOINT_TEMPLATE.replace(\"{region}\", region);\n this.#options.logger?.debug?.(\"Using Azure TTS endpoint:\", endpoint);\n\n return synthesizeSsml(ssml, { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs });\n }\n}\n","const AZURE_VOICE_API_VERSION = \"2025-10-01\";\n\nexport interface FetchAzureVoiceCatalogOptions {\n apiKey: string;\n region: string | string[];\n}\n\nexport interface AzureVoiceCatalogVoice {\n name: string;\n locale: string;\n secondaryLocales?: readonly string[];\n styles?: readonly string[];\n regions: readonly string[];\n status?: \"ga\" | \"preview\" | \"deprecated\";\n}\n\nexport interface FetchedAzureVoiceCatalogMetadata {\n voiceCount: number;\n generatedAt: string;\n apiVersion: string;\n regions: readonly string[];\n}\n\nexport interface AzureVoiceCatalog {\n voices: readonly AzureVoiceCatalogVoice[];\n metadata: FetchedAzureVoiceCatalogMetadata;\n}\n\ninterface AzureVoiceApiRecord {\n Locale?: unknown;\n Name?: unknown;\n SecondaryLocaleList?: unknown;\n ShortName?: unknown;\n Status?: unknown;\n StyleList?: unknown;\n}\n\nfunction stringValue(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim() ? value.trim() : undefined;\n}\n\nfunction stringList(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return [...new Set(value.map(stringValue).filter((item): item is string => item !== undefined))];\n}\n\nfunction normalizeStatus(value: unknown): AzureVoiceCatalogVoice[\"status\"] {\n const status = stringValue(value)?.toLowerCase();\n if (status === \"preview\" || status === \"deprecated\" || status === \"ga\") return status;\n return undefined;\n}\n\nfunction normalizeRegions(region: string | string[]): string[] {\n const regions = Array.isArray(region) ? region : [region];\n const result = [...new Set(regions.map((item) => item.trim()).filter(Boolean))];\n if (result.length === 0) throw new TypeError(\"At least one Azure Speech region is required.\");\n return result;\n}\n\nasync function fetchRegionVoices(region: string, apiKey: string): Promise<AzureVoiceApiRecord[]> {\n const endpoint = `https://${encodeURIComponent(region)}.tts.speech.microsoft.com/cognitiveservices/voices/list`;\n const response = await fetch(endpoint, {\n headers: {\n Accept: \"application/json\",\n \"Ocp-Apim-Subscription-Key\": apiKey,\n },\n });\n if (!response.ok) {\n throw new Error(`Azure List Voices API request failed for region \"${region}\" with HTTP ${response.status}.`);\n }\n const payload: unknown = await response.json();\n if (!Array.isArray(payload)) throw new Error(`Azure List Voices API returned an invalid response for \"${region}\".`);\n return payload.filter((item): item is AzureVoiceApiRecord => Boolean(item && typeof item === \"object\"));\n}\n\n/** Fetches and deduplicates the current Azure Speech voice catalog for one or more regions. */\nexport async function fetchAzureVoiceCatalog(options: FetchAzureVoiceCatalogOptions): Promise<AzureVoiceCatalog> {\n if (!options || typeof options.apiKey !== \"string\" || !options.apiKey.trim())\n throw new TypeError(\"An Azure Speech API key is required.\");\n const regions = normalizeRegions(options.region);\n const payloads = await Promise.all(regions.map((region) => fetchRegionVoices(region, options.apiKey)));\n const voices = new Map<string, AzureVoiceCatalogVoice>();\n\n for (let regionIndex = 0; regionIndex < payloads.length; regionIndex += 1) {\n const region = regions[regionIndex];\n for (const record of payloads[regionIndex]) {\n const name = stringValue(record.ShortName) ?? stringValue(record.Name);\n const locale = stringValue(record.Locale);\n if (!name || !locale) continue;\n const key = name.toLowerCase();\n const existing = voices.get(key);\n const secondaryLocales = stringList(record.SecondaryLocaleList);\n const styles = stringList(record.StyleList);\n const status = normalizeStatus(record.Status);\n const merged: AzureVoiceCatalogVoice = {\n name: existing?.name ?? name,\n locale: existing?.locale ?? locale,\n regions: [...new Set([...(existing?.regions ?? []), region])],\n };\n const mergedSecondaryLocales = [...new Set([...(existing?.secondaryLocales ?? []), ...secondaryLocales])];\n if (mergedSecondaryLocales.length > 0) merged.secondaryLocales = mergedSecondaryLocales;\n const mergedStyles = [...new Set([...(existing?.styles ?? []), ...styles])];\n if (mergedStyles.length > 0) merged.styles = mergedStyles;\n if (status) merged.status = status;\n else if (existing?.status) merged.status = existing.status;\n voices.set(key, merged);\n }\n }\n\n const sortedVoices = [...voices.values()].sort((first, second) => first.name.localeCompare(second.name));\n return {\n voices: sortedVoices,\n metadata: {\n voiceCount: sortedVoices.length,\n generatedAt: new Date().toISOString(),\n apiVersion: AZURE_VOICE_API_VERSION,\n regions,\n },\n };\n}\n"],"mappings":"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","names":["SpeechSDK"]}
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { synthesizeSpeech } from "./synthesis.ts";
|
|
2
|
-
import type { AzureTtsClientOptions } from "./types.ts";
|
|
1
|
+
import { synthesizeSpeech, synthesizeSsml } from "./synthesis.ts";
|
|
2
|
+
import type { AzureTtsClientOptions, SsmlSynthesisResult } from "./types.ts";
|
|
3
3
|
|
|
4
4
|
const ENDPOINT_TEMPLATE = "https://{region}.tts.speech.microsoft.com/cognitiveservices/v1";
|
|
5
5
|
|
|
@@ -18,4 +18,12 @@ export class AzureTtsClient {
|
|
|
18
18
|
const config = { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs };
|
|
19
19
|
return synthesizeSpeech(ssml, config);
|
|
20
20
|
}
|
|
21
|
+
|
|
22
|
+
async synthesizeSsml(ssml: string): Promise<SsmlSynthesisResult> {
|
|
23
|
+
const { region, subscriptionKey, outputFormat, signal, timeoutMs } = this.#options;
|
|
24
|
+
const endpoint = this.#options.endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
25
|
+
this.#options.logger?.debug?.("Using Azure TTS endpoint:", endpoint);
|
|
26
|
+
|
|
27
|
+
return synthesizeSsml(ssml, { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs });
|
|
28
|
+
}
|
|
21
29
|
}
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,23 @@
|
|
|
2
2
|
* azure-tts-client: Azure Text-to-Speech client for SSML playback.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
export type {
|
|
5
|
+
export type {
|
|
6
|
+
AzureTtsClientOptions,
|
|
7
|
+
AzureTtsLogger,
|
|
8
|
+
SsmlSynthesisBookmark,
|
|
9
|
+
SsmlSynthesisBoundary,
|
|
10
|
+
SsmlSynthesisResult,
|
|
11
|
+
SsmlSynthesisViseme,
|
|
12
|
+
TtsConfig,
|
|
13
|
+
} from "./types.ts";
|
|
6
14
|
export { AzureTtsError, AzureTtsSdkError } from "./errors.ts";
|
|
7
15
|
export { AzureTtsClient } from "./client.ts";
|
|
8
16
|
export { synthesizeSpeech } from "./synthesis.ts";
|
|
17
|
+
export { synthesizeSsml } from "./synthesis.ts";
|
|
18
|
+
export { fetchAzureVoiceCatalog } from "./voiceCatalog.ts";
|
|
19
|
+
export type {
|
|
20
|
+
AzureVoiceCatalog,
|
|
21
|
+
AzureVoiceCatalogVoice,
|
|
22
|
+
FetchedAzureVoiceCatalogMetadata,
|
|
23
|
+
FetchAzureVoiceCatalogOptions,
|
|
24
|
+
} from "./voiceCatalog.ts";
|
package/src/synthesis.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as SpeechSDK from "microsoft-cognitiveservices-speech-sdk";
|
|
2
2
|
import { createSpeechSdkError } from "./errors.ts";
|
|
3
3
|
import { createSpeechConfig } from "./speechConfig.ts";
|
|
4
|
-
import type { TtsConfig } from "./types.ts";
|
|
4
|
+
import type { SsmlSynthesisResult, TtsConfig } from "./types.ts";
|
|
5
5
|
|
|
6
6
|
function closeSpeechResources(speechConfig: SpeechSDK.SpeechConfig, synthesizer: SpeechSDK.SpeechSynthesizer): void {
|
|
7
7
|
try {
|
|
@@ -13,7 +13,9 @@ function closeSpeechResources(speechConfig: SpeechSDK.SpeechConfig, synthesizer:
|
|
|
13
13
|
} catch {}
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
const ticksToMilliseconds = (ticks: number): number => Math.max(0, ticks) / 10_000;
|
|
17
|
+
|
|
18
|
+
export async function synthesizeSsml(ssml: string, config: TtsConfig): Promise<SsmlSynthesisResult> {
|
|
17
19
|
if (config.signal?.aborted) {
|
|
18
20
|
throw createSpeechSdkError("Speech synthesis was cancelled.");
|
|
19
21
|
}
|
|
@@ -21,7 +23,7 @@ export async function synthesizeSpeech(ssml: string, config: TtsConfig): Promise
|
|
|
21
23
|
const speechConfig = createSpeechConfig(config);
|
|
22
24
|
const synthesizer = new SpeechSDK.SpeechSynthesizer(speechConfig, null);
|
|
23
25
|
|
|
24
|
-
return await new Promise<
|
|
26
|
+
return await new Promise<SsmlSynthesisResult>((resolve, reject) => {
|
|
25
27
|
let resourcesClosed = false;
|
|
26
28
|
let settled = false;
|
|
27
29
|
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
@@ -43,6 +45,23 @@ export async function synthesizeSpeech(ssml: string, config: TtsConfig): Promise
|
|
|
43
45
|
reject(createSpeechSdkError(error));
|
|
44
46
|
};
|
|
45
47
|
|
|
48
|
+
const boundaries: SsmlSynthesisResult["boundaries"] = [];
|
|
49
|
+
const visemes: SsmlSynthesisResult["visemes"] = [];
|
|
50
|
+
const bookmarks: SsmlSynthesisResult["bookmarks"] = [];
|
|
51
|
+
synthesizer.wordBoundary = (_sender, event) => {
|
|
52
|
+
boundaries.push({
|
|
53
|
+
text: event.text,
|
|
54
|
+
audioOffsetMs: ticksToMilliseconds(event.audioOffset),
|
|
55
|
+
durationMs: ticksToMilliseconds(event.duration),
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
synthesizer.visemeReceived = (_sender, event) => {
|
|
59
|
+
visemes.push({ visemeId: event.visemeId, audioOffsetMs: ticksToMilliseconds(event.audioOffset) });
|
|
60
|
+
};
|
|
61
|
+
synthesizer.bookmarkReached = (_sender, event) => {
|
|
62
|
+
bookmarks.push({ name: event.text, audioOffsetMs: ticksToMilliseconds(event.audioOffset) });
|
|
63
|
+
};
|
|
64
|
+
|
|
46
65
|
const cb = (result: SpeechSDK.SpeechSynthesisResult) => {
|
|
47
66
|
if (settled) return;
|
|
48
67
|
const { reason, errorDetails } = result;
|
|
@@ -54,7 +73,20 @@ export async function synthesizeSpeech(ssml: string, config: TtsConfig): Promise
|
|
|
54
73
|
settled = true;
|
|
55
74
|
cleanup();
|
|
56
75
|
closeResources();
|
|
57
|
-
|
|
76
|
+
const eventDurationMs = Math.max(
|
|
77
|
+
0,
|
|
78
|
+
...(boundaries ?? []).map((boundary) => boundary.audioOffsetMs + boundary.durationMs),
|
|
79
|
+
...(visemes ?? []).map((viseme) => viseme.audioOffsetMs),
|
|
80
|
+
...(bookmarks ?? []).map((bookmark) => bookmark.audioOffsetMs),
|
|
81
|
+
);
|
|
82
|
+
const durationMs = result.audioDuration ? ticksToMilliseconds(result.audioDuration) : eventDurationMs;
|
|
83
|
+
resolve({
|
|
84
|
+
audioData: result.audioData,
|
|
85
|
+
durationMs,
|
|
86
|
+
...(boundaries.length > 0 ? { boundaries, wordBoundary: boundaries, wordBoundaries: boundaries } : {}),
|
|
87
|
+
...(visemes.length > 0 ? { visemes } : {}),
|
|
88
|
+
...(bookmarks.length > 0 ? { bookmarks } : {}),
|
|
89
|
+
});
|
|
58
90
|
};
|
|
59
91
|
|
|
60
92
|
try {
|
|
@@ -74,3 +106,8 @@ export async function synthesizeSpeech(ssml: string, config: TtsConfig): Promise
|
|
|
74
106
|
}
|
|
75
107
|
});
|
|
76
108
|
}
|
|
109
|
+
|
|
110
|
+
/** Backward-compatible audio-only synthesis helper. */
|
|
111
|
+
export async function synthesizeSpeech(ssml: string, config: TtsConfig): Promise<ArrayBuffer> {
|
|
112
|
+
return (await synthesizeSsml(ssml, config)).audioData;
|
|
113
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -7,6 +7,35 @@ export interface TtsConfig {
|
|
|
7
7
|
outputFormat?: string;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
export interface SsmlSynthesisBoundary {
|
|
11
|
+
text: string;
|
|
12
|
+
audioOffsetMs: number;
|
|
13
|
+
durationMs: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface SsmlSynthesisViseme {
|
|
17
|
+
visemeId: number;
|
|
18
|
+
audioOffsetMs: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface SsmlSynthesisBookmark {
|
|
22
|
+
name: string;
|
|
23
|
+
audioOffsetMs: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Audio and Azure Speech synchronization events emitted for one SSML request. */
|
|
27
|
+
export interface SsmlSynthesisResult {
|
|
28
|
+
audioData: ArrayBuffer;
|
|
29
|
+
durationMs: number;
|
|
30
|
+
boundaries?: SsmlSynthesisBoundary[];
|
|
31
|
+
/** Alias matching the Azure Speech event name. */
|
|
32
|
+
wordBoundary?: SsmlSynthesisBoundary[];
|
|
33
|
+
/** Alias for consumers that use Azure's word-boundary terminology. */
|
|
34
|
+
wordBoundaries?: SsmlSynthesisBoundary[];
|
|
35
|
+
visemes?: SsmlSynthesisViseme[];
|
|
36
|
+
bookmarks?: SsmlSynthesisBookmark[];
|
|
37
|
+
}
|
|
38
|
+
|
|
10
39
|
export interface AzureTtsLogger {
|
|
11
40
|
debug?: (...args: unknown[]) => void;
|
|
12
41
|
info?: (...args: unknown[]) => void;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
const AZURE_VOICE_API_VERSION = "2025-10-01";
|
|
2
|
+
|
|
3
|
+
export interface FetchAzureVoiceCatalogOptions {
|
|
4
|
+
apiKey: string;
|
|
5
|
+
region: string | string[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface AzureVoiceCatalogVoice {
|
|
9
|
+
name: string;
|
|
10
|
+
locale: string;
|
|
11
|
+
secondaryLocales?: readonly string[];
|
|
12
|
+
styles?: readonly string[];
|
|
13
|
+
regions: readonly string[];
|
|
14
|
+
status?: "ga" | "preview" | "deprecated";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface FetchedAzureVoiceCatalogMetadata {
|
|
18
|
+
voiceCount: number;
|
|
19
|
+
generatedAt: string;
|
|
20
|
+
apiVersion: string;
|
|
21
|
+
regions: readonly string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface AzureVoiceCatalog {
|
|
25
|
+
voices: readonly AzureVoiceCatalogVoice[];
|
|
26
|
+
metadata: FetchedAzureVoiceCatalogMetadata;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface AzureVoiceApiRecord {
|
|
30
|
+
Locale?: unknown;
|
|
31
|
+
Name?: unknown;
|
|
32
|
+
SecondaryLocaleList?: unknown;
|
|
33
|
+
ShortName?: unknown;
|
|
34
|
+
Status?: unknown;
|
|
35
|
+
StyleList?: unknown;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function stringValue(value: unknown): string | undefined {
|
|
39
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function stringList(value: unknown): string[] {
|
|
43
|
+
if (!Array.isArray(value)) return [];
|
|
44
|
+
return [...new Set(value.map(stringValue).filter((item): item is string => item !== undefined))];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function normalizeStatus(value: unknown): AzureVoiceCatalogVoice["status"] {
|
|
48
|
+
const status = stringValue(value)?.toLowerCase();
|
|
49
|
+
if (status === "preview" || status === "deprecated" || status === "ga") return status;
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function normalizeRegions(region: string | string[]): string[] {
|
|
54
|
+
const regions = Array.isArray(region) ? region : [region];
|
|
55
|
+
const result = [...new Set(regions.map((item) => item.trim()).filter(Boolean))];
|
|
56
|
+
if (result.length === 0) throw new TypeError("At least one Azure Speech region is required.");
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function fetchRegionVoices(region: string, apiKey: string): Promise<AzureVoiceApiRecord[]> {
|
|
61
|
+
const endpoint = `https://${encodeURIComponent(region)}.tts.speech.microsoft.com/cognitiveservices/voices/list`;
|
|
62
|
+
const response = await fetch(endpoint, {
|
|
63
|
+
headers: {
|
|
64
|
+
Accept: "application/json",
|
|
65
|
+
"Ocp-Apim-Subscription-Key": apiKey,
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
throw new Error(`Azure List Voices API request failed for region "${region}" with HTTP ${response.status}.`);
|
|
70
|
+
}
|
|
71
|
+
const payload: unknown = await response.json();
|
|
72
|
+
if (!Array.isArray(payload)) throw new Error(`Azure List Voices API returned an invalid response for "${region}".`);
|
|
73
|
+
return payload.filter((item): item is AzureVoiceApiRecord => Boolean(item && typeof item === "object"));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Fetches and deduplicates the current Azure Speech voice catalog for one or more regions. */
|
|
77
|
+
export async function fetchAzureVoiceCatalog(options: FetchAzureVoiceCatalogOptions): Promise<AzureVoiceCatalog> {
|
|
78
|
+
if (!options || typeof options.apiKey !== "string" || !options.apiKey.trim())
|
|
79
|
+
throw new TypeError("An Azure Speech API key is required.");
|
|
80
|
+
const regions = normalizeRegions(options.region);
|
|
81
|
+
const payloads = await Promise.all(regions.map((region) => fetchRegionVoices(region, options.apiKey)));
|
|
82
|
+
const voices = new Map<string, AzureVoiceCatalogVoice>();
|
|
83
|
+
|
|
84
|
+
for (let regionIndex = 0; regionIndex < payloads.length; regionIndex += 1) {
|
|
85
|
+
const region = regions[regionIndex];
|
|
86
|
+
for (const record of payloads[regionIndex]) {
|
|
87
|
+
const name = stringValue(record.ShortName) ?? stringValue(record.Name);
|
|
88
|
+
const locale = stringValue(record.Locale);
|
|
89
|
+
if (!name || !locale) continue;
|
|
90
|
+
const key = name.toLowerCase();
|
|
91
|
+
const existing = voices.get(key);
|
|
92
|
+
const secondaryLocales = stringList(record.SecondaryLocaleList);
|
|
93
|
+
const styles = stringList(record.StyleList);
|
|
94
|
+
const status = normalizeStatus(record.Status);
|
|
95
|
+
const merged: AzureVoiceCatalogVoice = {
|
|
96
|
+
name: existing?.name ?? name,
|
|
97
|
+
locale: existing?.locale ?? locale,
|
|
98
|
+
regions: [...new Set([...(existing?.regions ?? []), region])],
|
|
99
|
+
};
|
|
100
|
+
const mergedSecondaryLocales = [...new Set([...(existing?.secondaryLocales ?? []), ...secondaryLocales])];
|
|
101
|
+
if (mergedSecondaryLocales.length > 0) merged.secondaryLocales = mergedSecondaryLocales;
|
|
102
|
+
const mergedStyles = [...new Set([...(existing?.styles ?? []), ...styles])];
|
|
103
|
+
if (mergedStyles.length > 0) merged.styles = mergedStyles;
|
|
104
|
+
if (status) merged.status = status;
|
|
105
|
+
else if (existing?.status) merged.status = existing.status;
|
|
106
|
+
voices.set(key, merged);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const sortedVoices = [...voices.values()].sort((first, second) => first.name.localeCompare(second.name));
|
|
111
|
+
return {
|
|
112
|
+
voices: sortedVoices,
|
|
113
|
+
metadata: {
|
|
114
|
+
voiceCount: sortedVoices.length,
|
|
115
|
+
generatedAt: new Date().toISOString(),
|
|
116
|
+
apiVersion: AZURE_VOICE_API_VERSION,
|
|
117
|
+
regions,
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
package/test/synthesis.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test, { type TestContext } from "node:test";
|
|
3
3
|
import * as SpeechSDK from "microsoft-cognitiveservices-speech-sdk";
|
|
4
|
-
import { synthesizeSpeech } from "../src/index.ts";
|
|
4
|
+
import { synthesizeSpeech, synthesizeSsml } from "../src/index.ts";
|
|
5
5
|
|
|
6
6
|
const endpoint = "https://speech.example.test/cognitiveservices/v1";
|
|
7
7
|
const subscriptionKey = "subscription-key";
|
|
@@ -51,6 +51,45 @@ test("synthesizeSpeech aborts the SDK request and settles its promise", async (t
|
|
|
51
51
|
assert.equal(closeCount, 1);
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
+
test("synthesizeSsml returns word boundaries, visemes, bookmarks, and duration", async (t) => {
|
|
55
|
+
const originalFromEndpoint = SpeechSDK.SpeechConfig.fromEndpoint;
|
|
56
|
+
t.mock.method(SpeechSDK.SpeechConfig, "fromEndpoint", (speechEndpoint, key) =>
|
|
57
|
+
originalFromEndpoint(speechEndpoint, String(key)),
|
|
58
|
+
);
|
|
59
|
+
t.mock.method(
|
|
60
|
+
SpeechSDK.SpeechSynthesizer.prototype,
|
|
61
|
+
"speakSsmlAsync",
|
|
62
|
+
function (this: SpeechSDK.SpeechSynthesizer, _ssml, callback) {
|
|
63
|
+
this.wordBoundary?.(this, {
|
|
64
|
+
text: "Hello",
|
|
65
|
+
audioOffset: 1_000_000,
|
|
66
|
+
duration: 250_000,
|
|
67
|
+
} as SpeechSDK.SpeechSynthesisWordBoundaryEventArgs);
|
|
68
|
+
this.visemeReceived?.(this, { visemeId: 4, audioOffset: 2_000_000 } as SpeechSDK.SpeechSynthesisVisemeEventArgs);
|
|
69
|
+
this.bookmarkReached?.(this, {
|
|
70
|
+
text: "chapter-1",
|
|
71
|
+
audioOffset: 3_000_000,
|
|
72
|
+
} as SpeechSDK.SpeechSynthesisBookmarkEventArgs);
|
|
73
|
+
callback?.({
|
|
74
|
+
audioData: new ArrayBuffer(2),
|
|
75
|
+
audioDuration: 4_000_000,
|
|
76
|
+
errorDetails: "",
|
|
77
|
+
reason: SpeechSDK.ResultReason.SynthesizingAudioCompleted,
|
|
78
|
+
} as SpeechSDK.SpeechSynthesisResult);
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
t.mock.method(SpeechSDK.SpeechSynthesizer.prototype, "close", () => undefined);
|
|
82
|
+
|
|
83
|
+
const result = await synthesizeSsml("<speak>Hello</speak>", { endpoint, subscriptionKey, region });
|
|
84
|
+
|
|
85
|
+
assert.deepEqual(result.boundaries, [{ text: "Hello", audioOffsetMs: 100, durationMs: 25 }]);
|
|
86
|
+
assert.deepEqual(result.wordBoundary, result.boundaries);
|
|
87
|
+
assert.deepEqual(result.wordBoundaries, result.boundaries);
|
|
88
|
+
assert.deepEqual(result.visemes, [{ visemeId: 4, audioOffsetMs: 200 }]);
|
|
89
|
+
assert.deepEqual(result.bookmarks, [{ name: "chapter-1", audioOffsetMs: 300 }]);
|
|
90
|
+
assert.equal(result.durationMs, 400);
|
|
91
|
+
});
|
|
92
|
+
|
|
54
93
|
test("synthesizeSpeech aborts the SDK request on timeout and settles its promise", async (t) => {
|
|
55
94
|
let closeCount = 0;
|
|
56
95
|
let resultCallback: ((result: SpeechSDK.SpeechSynthesisResult) => void) | undefined;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { fetchAzureVoiceCatalog } from "../src/index.ts";
|
|
4
|
+
|
|
5
|
+
test("fetchAzureVoiceCatalog fetches, merges, and annotates voices across regions", async () => {
|
|
6
|
+
const originalFetch = globalThis.fetch;
|
|
7
|
+
const requests: string[] = [];
|
|
8
|
+
globalThis.fetch = async (input) => {
|
|
9
|
+
const url = String(input);
|
|
10
|
+
requests.push(url);
|
|
11
|
+
const region = url.split(".")[0]?.replace("https://", "");
|
|
12
|
+
return new Response(
|
|
13
|
+
JSON.stringify(
|
|
14
|
+
region === "eastus"
|
|
15
|
+
? [
|
|
16
|
+
{ Locale: "fil-PH", ShortName: "fil-PH-BlessicaNeural", StyleList: ["cheerful"] },
|
|
17
|
+
{ Locale: "en-US", Name: "en-US-JennyNeural", StyleList: ["chat"] },
|
|
18
|
+
]
|
|
19
|
+
: [{ Locale: "fil-PH", ShortName: "fil-PH-BlessicaNeural", StyleList: ["sad"] }],
|
|
20
|
+
),
|
|
21
|
+
{ status: 200, headers: { "content-type": "application/json" } },
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
try {
|
|
25
|
+
const catalog = await fetchAzureVoiceCatalog({ apiKey: "secret", region: ["eastus", "japaneast"] });
|
|
26
|
+
assert.equal(requests.length, 2);
|
|
27
|
+
assert.deepEqual(catalog.metadata.regions, ["eastus", "japaneast"]);
|
|
28
|
+
assert.equal(catalog.metadata.voiceCount, 2);
|
|
29
|
+
assert.deepEqual(
|
|
30
|
+
catalog.voices.find(({ name }) => name === "fil-PH-BlessicaNeural"),
|
|
31
|
+
{
|
|
32
|
+
name: "fil-PH-BlessicaNeural",
|
|
33
|
+
locale: "fil-PH",
|
|
34
|
+
styles: ["cheerful", "sad"],
|
|
35
|
+
regions: ["eastus", "japaneast"],
|
|
36
|
+
},
|
|
37
|
+
);
|
|
38
|
+
} finally {
|
|
39
|
+
globalThis.fetch = originalFetch;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("fetchAzureVoiceCatalog reports HTTP and payload failures", async () => {
|
|
44
|
+
const originalFetch = globalThis.fetch;
|
|
45
|
+
globalThis.fetch = async () => new Response("no", { status: 401 });
|
|
46
|
+
try {
|
|
47
|
+
await assert.rejects(fetchAzureVoiceCatalog({ apiKey: "secret", region: "eastus" }), /HTTP 401/);
|
|
48
|
+
} finally {
|
|
49
|
+
globalThis.fetch = originalFetch;
|
|
50
|
+
}
|
|
51
|
+
});
|