ssml-builder-js 2.14.0 → 2.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/dist/{chunk-UZSCXPC5.mjs → chunk-BDY2Q2JL.mjs} +2 -2
- package/dist/{chunk-AQ55MOPU.mjs → chunk-FXUM45ZY.mjs} +305 -173
- package/dist/chunk-FXUM45ZY.mjs.map +1 -0
- package/dist/{chunk-QFIBPCO4.mjs → chunk-WXFLUCLR.mjs} +164 -17
- package/dist/chunk-WXFLUCLR.mjs.map +1 -0
- package/dist/core.d.mts +48 -17
- package/dist/core.d.ts +48 -17
- package/dist/core.js +303 -170
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +3 -1
- package/dist/elements.js +38 -16
- package/dist/elements.js.map +1 -1
- package/dist/elements.mjs +2 -2
- package/dist/{index.d-CUXRwSw0.d.mts → index.d-8BvkB9gz.d.mts} +22 -2
- package/dist/{index.d-CUXRwSw0.d.ts → index.d-8BvkB9gz.d.ts} +22 -2
- package/dist/index.d.mts +114 -18
- package/dist/index.d.ts +114 -18
- package/dist/index.js +1783 -1281
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +307 -86
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +11 -2
- package/dist/react.d.ts +11 -2
- package/dist/react.js +115 -28
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +79 -14
- package/dist/react.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-AQ55MOPU.mjs.map +0 -1
- package/dist/chunk-QFIBPCO4.mjs.map +0 -1
- /package/dist/{chunk-UZSCXPC5.mjs.map → chunk-BDY2Q2JL.mjs.map} +0 -0
package/dist/index.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
fromPlainTextToSsml,
|
|
9
9
|
getAzureVoiceCatalogMetadata,
|
|
10
10
|
getBuiltInVoiceCatalogMetadata,
|
|
11
|
+
getSsmlSourceMap,
|
|
11
12
|
isValidAzureAudioDuration,
|
|
12
13
|
mapSsmlTextNodes,
|
|
13
14
|
normalizeAzureLanguage,
|
|
@@ -16,10 +17,11 @@ import {
|
|
|
16
17
|
validateAzureSsml,
|
|
17
18
|
validateSsml,
|
|
18
19
|
validateSsmlStructureIntegrity
|
|
19
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-FXUM45ZY.mjs";
|
|
20
21
|
import {
|
|
22
|
+
getSsmlSourceMap as getSsmlSourceMap2,
|
|
21
23
|
validateAzureSsml as validateAzureSsml2
|
|
22
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-WXFLUCLR.mjs";
|
|
23
25
|
import {
|
|
24
26
|
__privateAdd,
|
|
25
27
|
__privateGet,
|
|
@@ -30,6 +32,7 @@ import {
|
|
|
30
32
|
var AzureTtsError = class extends Error {
|
|
31
33
|
constructor(status, statusText, responseBody, requestId) {
|
|
32
34
|
super(`Azure TTS request failed: ${status} ${statusText}`);
|
|
35
|
+
this.kind = "azure-api-error";
|
|
33
36
|
this.name = "AzureTtsError";
|
|
34
37
|
this.status = status;
|
|
35
38
|
this.statusText = statusText;
|
|
@@ -45,13 +48,44 @@ var AzureTtsSdkError = class extends AzureTtsError {
|
|
|
45
48
|
this.errorDetails = errorDetails;
|
|
46
49
|
}
|
|
47
50
|
};
|
|
51
|
+
var SynthesisCancelledError = class extends Error {
|
|
52
|
+
constructor(message = "Speech synthesis was cancelled.") {
|
|
53
|
+
super(message);
|
|
54
|
+
this.kind = "cancelled";
|
|
55
|
+
this.name = "SynthesisCancelledError";
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
var SynthesisTimeoutError = class extends Error {
|
|
59
|
+
constructor(message) {
|
|
60
|
+
super(message);
|
|
61
|
+
this.kind = "timeout";
|
|
62
|
+
this.name = "SynthesisTimeoutError";
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
var MergeError = class extends Error {
|
|
66
|
+
constructor(message, cause) {
|
|
67
|
+
super(message);
|
|
68
|
+
this.kind = "merge-error";
|
|
69
|
+
this.name = "MergeError";
|
|
70
|
+
this.cause = cause;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
48
73
|
var UnsupportedMergeFormatError = class extends Error {
|
|
49
74
|
constructor(format) {
|
|
50
75
|
super(`Audio format "${format}" cannot be safely concatenated; container re-multiplexing is required.`);
|
|
76
|
+
this.kind = "unsupported-format-error";
|
|
51
77
|
this.name = "UnsupportedMergeFormatError";
|
|
52
78
|
this.format = format;
|
|
53
79
|
}
|
|
54
80
|
};
|
|
81
|
+
function toSynthesisError(error) {
|
|
82
|
+
if (error instanceof AzureTtsError || error instanceof MergeError || error instanceof UnsupportedMergeFormatError || error instanceof SynthesisCancelledError || error instanceof SynthesisTimeoutError)
|
|
83
|
+
return error;
|
|
84
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
85
|
+
if (/cancel|abort/i.test(message)) return new SynthesisCancelledError(message);
|
|
86
|
+
if (/tim(?:e|ed) ?out/i.test(message)) return new SynthesisTimeoutError(message);
|
|
87
|
+
return createSpeechSdkError(error);
|
|
88
|
+
}
|
|
55
89
|
function createSpeechSdkError(error) {
|
|
56
90
|
const message = error instanceof Error ? error.message : String(error);
|
|
57
91
|
return new AzureTtsSdkError(message);
|
|
@@ -60,9 +94,6 @@ function createSpeechSdkError(error) {
|
|
|
60
94
|
// packages/azure-tts-client/src/synthesis.ts
|
|
61
95
|
import * as SpeechSDK2 from "microsoft-cognitiveservices-speech-sdk";
|
|
62
96
|
|
|
63
|
-
// packages/azure-tts-client/src/speechConfig.ts
|
|
64
|
-
import { SpeechConfig } from "microsoft-cognitiveservices-speech-sdk";
|
|
65
|
-
|
|
66
97
|
// packages/azure-tts-client/src/outputFormats.ts
|
|
67
98
|
import * as SpeechSDK from "microsoft-cognitiveservices-speech-sdk";
|
|
68
99
|
var DEFAULT_OUTPUT_FORMAT = "audio-16khz-128kbitrate-mono-mp3";
|
|
@@ -107,6 +138,14 @@ var OUTPUT_FORMATS = {
|
|
|
107
138
|
"amr-wb-16000hz": SpeechSDK.SpeechSynthesisOutputFormat.AmrWb16000Hz,
|
|
108
139
|
"g722-16khz-64kbps": SpeechSDK.SpeechSynthesisOutputFormat.G72216Khz64Kbps
|
|
109
140
|
};
|
|
141
|
+
function resolveMimeType(outputFormat) {
|
|
142
|
+
if (/(?:wav|wave|riff)/i.test(outputFormat)) return "audio/wav";
|
|
143
|
+
if (/(?:mp3|mpeg)/i.test(outputFormat)) return "audio/mpeg";
|
|
144
|
+
if (/ogg/i.test(outputFormat)) return "audio/ogg";
|
|
145
|
+
if (/webm/i.test(outputFormat)) return "audio/webm";
|
|
146
|
+
if (/raw/i.test(outputFormat)) return "audio/L16";
|
|
147
|
+
return "application/octet-stream";
|
|
148
|
+
}
|
|
110
149
|
function resolveOutputFormat(outputFormat) {
|
|
111
150
|
const resolvedFormat = OUTPUT_FORMATS[outputFormat];
|
|
112
151
|
if (resolvedFormat === void 0) {
|
|
@@ -116,6 +155,7 @@ function resolveOutputFormat(outputFormat) {
|
|
|
116
155
|
}
|
|
117
156
|
|
|
118
157
|
// packages/azure-tts-client/src/speechConfig.ts
|
|
158
|
+
import { SpeechConfig } from "microsoft-cognitiveservices-speech-sdk";
|
|
119
159
|
function resolveEndpoint(config) {
|
|
120
160
|
const endpoint = config.endpoint?.trim() || "https://{region}.tts.speech.microsoft.com/cognitiveservices/v1";
|
|
121
161
|
return endpoint.replace(/\{region\}/g, encodeURIComponent(config.region));
|
|
@@ -249,28 +289,35 @@ function resolveMergeAudioFormat(format) {
|
|
|
249
289
|
function canMergeAudioFormat(format) {
|
|
250
290
|
return resolveMergeAudioFormat(format) !== void 0;
|
|
251
291
|
}
|
|
252
|
-
function mergeAudioBuffers(buffers,
|
|
253
|
-
|
|
254
|
-
if (
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
output.
|
|
260
|
-
offset
|
|
292
|
+
function mergeAudioBuffers(buffers, options) {
|
|
293
|
+
const format = typeof options === "string" ? options : options?.format;
|
|
294
|
+
if (!format) throw new UnsupportedMergeFormatError("");
|
|
295
|
+
try {
|
|
296
|
+
if (isWavFormat(format)) return mergeWavBuffers(buffers);
|
|
297
|
+
if (isMp3Format(format)) {
|
|
298
|
+
const parts = buffers.map(stripMp3Tags);
|
|
299
|
+
const output = new Uint8Array(parts.reduce((total, part) => total + part.byteLength, 0));
|
|
300
|
+
let offset = 0;
|
|
301
|
+
for (const part of parts) {
|
|
302
|
+
output.set(part, offset);
|
|
303
|
+
offset += part.byteLength;
|
|
304
|
+
}
|
|
305
|
+
return output.buffer;
|
|
261
306
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
307
|
+
if (isRawFormat(format)) {
|
|
308
|
+
const output = new Uint8Array(buffers.reduce((total, buffer) => total + buffer.byteLength, 0));
|
|
309
|
+
let offset = 0;
|
|
310
|
+
for (const buffer of buffers) {
|
|
311
|
+
output.set(new Uint8Array(buffer), offset);
|
|
312
|
+
offset += buffer.byteLength;
|
|
313
|
+
}
|
|
314
|
+
return output.buffer;
|
|
270
315
|
}
|
|
271
|
-
|
|
316
|
+
throw new UnsupportedMergeFormatError(format);
|
|
317
|
+
} catch (error) {
|
|
318
|
+
if (error instanceof UnsupportedMergeFormatError || error instanceof MergeError) throw error;
|
|
319
|
+
throw new MergeError(`Audio buffers could not be merged for format "${format}".`, error);
|
|
272
320
|
}
|
|
273
|
-
throw new UnsupportedMergeFormatError(format);
|
|
274
321
|
}
|
|
275
322
|
function closeSpeechResources(speechConfig, synthesizer) {
|
|
276
323
|
try {
|
|
@@ -285,7 +332,7 @@ function closeSpeechResources(speechConfig, synthesizer) {
|
|
|
285
332
|
var ticksToMilliseconds = (ticks) => Math.max(0, ticks) / 1e4;
|
|
286
333
|
async function synthesizeSsml(ssml, config) {
|
|
287
334
|
if (config.signal?.aborted) {
|
|
288
|
-
throw
|
|
335
|
+
throw new SynthesisCancelledError();
|
|
289
336
|
}
|
|
290
337
|
const speechConfig = createSpeechConfig(config);
|
|
291
338
|
const synthesizer = new SpeechSDK2.SpeechSynthesizer(speechConfig, null);
|
|
@@ -308,23 +355,94 @@ async function synthesizeSsml(ssml, config) {
|
|
|
308
355
|
settled = true;
|
|
309
356
|
cleanup();
|
|
310
357
|
closeResources();
|
|
311
|
-
reject(
|
|
358
|
+
reject(toSynthesisError(error));
|
|
312
359
|
};
|
|
313
360
|
const boundaries = [];
|
|
314
361
|
const visemes = [];
|
|
315
362
|
const bookmarks = [];
|
|
363
|
+
let sourceEventCursor = 0;
|
|
364
|
+
let generatedSourceMap;
|
|
365
|
+
if (!config.sourceTextSegments && !config.sourceMarkers) {
|
|
366
|
+
try {
|
|
367
|
+
generatedSourceMap = getSsmlSourceMap2(ssml);
|
|
368
|
+
} catch {
|
|
369
|
+
generatedSourceMap = void 0;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
const sourceBaseOffset = config.sourceTextRange?.start ?? 0;
|
|
373
|
+
const sourceSegments = config.sourceTextSegments ?? generatedSourceMap?.segments.map((segment) => ({
|
|
374
|
+
...segment,
|
|
375
|
+
range: {
|
|
376
|
+
start: segment.range.start + sourceBaseOffset,
|
|
377
|
+
end: segment.range.end + sourceBaseOffset
|
|
378
|
+
},
|
|
379
|
+
sourceNodePath: [...segment.sourceNodePath]
|
|
380
|
+
})) ?? [];
|
|
381
|
+
const sourceMarkers = config.sourceMarkers ?? generatedSourceMap?.markers.map((marker) => ({
|
|
382
|
+
...marker,
|
|
383
|
+
originalTextRange: {
|
|
384
|
+
start: marker.originalTextRange.start + sourceBaseOffset,
|
|
385
|
+
end: marker.originalTextRange.end + sourceBaseOffset
|
|
386
|
+
},
|
|
387
|
+
sourceNodePath: [...marker.sourceNodePath]
|
|
388
|
+
})) ?? [];
|
|
389
|
+
const sourceText = sourceSegments.map((segment) => segment.text).join("");
|
|
390
|
+
const mapSourceEvent = (text, offsetHint, markerName) => {
|
|
391
|
+
const marker = markerName ? sourceMarkers.find((candidate) => candidate.name === markerName) : void 0;
|
|
392
|
+
if (marker) {
|
|
393
|
+
return {
|
|
394
|
+
originalTextRange: { ...marker.originalTextRange },
|
|
395
|
+
sourceNodePath: [...marker.sourceNodePath],
|
|
396
|
+
textRange: { ...marker.originalTextRange }
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
if (sourceSegments.length === 0 && !config.sourceTextRange && !config.sourceNodePath) return {};
|
|
400
|
+
const value = text ?? "";
|
|
401
|
+
let localStart = Number.isFinite(offsetHint) && (offsetHint ?? 0) >= 0 ? offsetHint : -1;
|
|
402
|
+
if (value && localStart >= 0 && sourceText.slice(localStart, localStart + value.length) !== value)
|
|
403
|
+
localStart = -1;
|
|
404
|
+
if (localStart < 0 || localStart > sourceText.length) {
|
|
405
|
+
localStart = value ? sourceText.indexOf(value, sourceEventCursor) : sourceEventCursor;
|
|
406
|
+
if (localStart < 0) localStart = value ? sourceText.indexOf(value) : sourceEventCursor;
|
|
407
|
+
}
|
|
408
|
+
localStart = Math.max(0, localStart);
|
|
409
|
+
const localEnd = Math.min(sourceText.length, localStart + value.length);
|
|
410
|
+
sourceEventCursor = Math.max(sourceEventCursor, localEnd);
|
|
411
|
+
const baseStart = config.sourceTextRange?.start ?? sourceSegments[0]?.range.start ?? 0;
|
|
412
|
+
const fallbackRange = { start: baseStart + localStart, end: baseStart + localEnd };
|
|
413
|
+
const segment = sourceSegments.find(({ range }) => range.start <= fallbackRange.start && range.end > fallbackRange.start) ?? sourceSegments.find(({ range }) => range.end > fallbackRange.start) ?? (value.length === 0 ? sourceSegments.find(({ range }) => range.start <= fallbackRange.start && range.end >= fallbackRange.start) : void 0);
|
|
414
|
+
return {
|
|
415
|
+
originalTextRange: { ...fallbackRange },
|
|
416
|
+
textRange: { ...fallbackRange },
|
|
417
|
+
...segment ? { sourceNodePath: [...segment.sourceNodePath] } : config.sourceNodePath ? { sourceNodePath: [...config.sourceNodePath] } : {}
|
|
418
|
+
};
|
|
419
|
+
};
|
|
316
420
|
synthesizer.wordBoundary = (_sender, event) => {
|
|
317
421
|
boundaries.push({
|
|
318
422
|
text: event.text,
|
|
319
423
|
audioOffsetMs: ticksToMilliseconds(event.audioOffset),
|
|
320
|
-
durationMs: ticksToMilliseconds(event.duration)
|
|
424
|
+
durationMs: ticksToMilliseconds(event.duration),
|
|
425
|
+
...mapSourceEvent(
|
|
426
|
+
event.text,
|
|
427
|
+
event.textOffset
|
|
428
|
+
)
|
|
321
429
|
});
|
|
322
430
|
};
|
|
323
431
|
synthesizer.visemeReceived = (_sender, event) => {
|
|
324
|
-
|
|
432
|
+
const eventWithOffset = event;
|
|
433
|
+
visemes.push({
|
|
434
|
+
visemeId: event.visemeId,
|
|
435
|
+
audioOffsetMs: ticksToMilliseconds(event.audioOffset),
|
|
436
|
+
...mapSourceEvent(void 0, eventWithOffset.textOffset)
|
|
437
|
+
});
|
|
325
438
|
};
|
|
326
439
|
synthesizer.bookmarkReached = (_sender, event) => {
|
|
327
|
-
|
|
440
|
+
const eventWithOffset = event;
|
|
441
|
+
bookmarks.push({
|
|
442
|
+
name: event.text,
|
|
443
|
+
audioOffsetMs: ticksToMilliseconds(event.audioOffset),
|
|
444
|
+
...mapSourceEvent(void 0, eventWithOffset.textOffset, event.text)
|
|
445
|
+
});
|
|
328
446
|
};
|
|
329
447
|
const cb = (result) => {
|
|
330
448
|
if (settled) return;
|
|
@@ -347,8 +465,8 @@ async function synthesizeSsml(ssml, config) {
|
|
|
347
465
|
const requestId = result.resultId;
|
|
348
466
|
const addSourceMetadata = (event) => ({
|
|
349
467
|
...event,
|
|
350
|
-
...config.sourceTextRange ? { textRange: { ...config.sourceTextRange } } : {},
|
|
351
|
-
...config.sourceTextRange ? { originalTextRange: { ...config.sourceTextRange } } : {},
|
|
468
|
+
...config.sourceTextRange && !("textRange" in event) ? { textRange: { ...config.sourceTextRange } } : {},
|
|
469
|
+
...config.sourceTextRange && !("originalTextRange" in event) ? { originalTextRange: { ...config.sourceTextRange } } : {},
|
|
352
470
|
...config.chunkIndex !== void 0 ? { chunkIndex: config.chunkIndex } : {},
|
|
353
471
|
...config.sourceNodePath ? { sourceNodePath: [...config.sourceNodePath] } : {},
|
|
354
472
|
...requestId ? { requestId } : {}
|
|
@@ -368,12 +486,12 @@ async function synthesizeSsml(ssml, config) {
|
|
|
368
486
|
};
|
|
369
487
|
try {
|
|
370
488
|
if (config.signal) {
|
|
371
|
-
abortHandler = () => rejectWithError(
|
|
489
|
+
abortHandler = () => rejectWithError(new SynthesisCancelledError());
|
|
372
490
|
config.signal.addEventListener("abort", abortHandler, { once: true });
|
|
373
491
|
}
|
|
374
492
|
if (config.timeoutMs !== void 0 && config.timeoutMs > 0) {
|
|
375
493
|
timeout = setTimeout(
|
|
376
|
-
() => rejectWithError(`Speech synthesis timed out after ${config.timeoutMs} ms.`),
|
|
494
|
+
() => rejectWithError(new SynthesisTimeoutError(`Speech synthesis timed out after ${config.timeoutMs} ms.`)),
|
|
377
495
|
config.timeoutMs
|
|
378
496
|
);
|
|
379
497
|
}
|
|
@@ -415,7 +533,9 @@ async function synthesizeSsmlChunks(chunks, config) {
|
|
|
415
533
|
const result = await synthesizeSsml(input.ssml, {
|
|
416
534
|
...config,
|
|
417
535
|
...input.originalTextRange ? { sourceTextRange: input.originalTextRange } : {},
|
|
418
|
-
...input.sourceNodePath ? { sourceNodePath: input.sourceNodePath } : {},
|
|
536
|
+
...input.sourceNodePath ?? config.sourceNodePath ? { sourceNodePath: [...input.sourceNodePath ?? config.sourceNodePath ?? []] } : {},
|
|
537
|
+
...input.sourceTextSegments ? { sourceTextSegments: input.sourceTextSegments } : {},
|
|
538
|
+
...input.sourceMarkers ? { sourceMarkers: input.sourceMarkers } : {},
|
|
419
539
|
chunkIndex: index,
|
|
420
540
|
onProgress: void 0
|
|
421
541
|
});
|
|
@@ -443,27 +563,16 @@ async function synthesizeSsmlChunks(chunks, config) {
|
|
|
443
563
|
throw error;
|
|
444
564
|
}
|
|
445
565
|
}
|
|
446
|
-
return mergeSynthesisResults(results,
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
results.map((result) => result.audioData),
|
|
452
|
-
format
|
|
453
|
-
)
|
|
454
|
-
) : new Uint8Array(results.reduce((total, result) => total + result.audioData.byteLength, 0));
|
|
455
|
-
if (!format) {
|
|
456
|
-
let offset = 0;
|
|
457
|
-
for (const result of results) {
|
|
458
|
-
audioData.set(new Uint8Array(result.audioData), offset);
|
|
459
|
-
offset += result.audioData.byteLength;
|
|
460
|
-
}
|
|
461
|
-
}
|
|
566
|
+
return mergeSynthesisResults(results, {
|
|
567
|
+
format: config.outputFormat ?? "audio-16khz-128kbitrate-mono-mp3"
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
function createMergedResult(results, audioData, format) {
|
|
462
571
|
const boundaries = [];
|
|
463
572
|
const visemes = [];
|
|
464
573
|
const bookmarks = [];
|
|
465
574
|
let durationOffset = 0;
|
|
466
|
-
for (const result of results) {
|
|
575
|
+
for (const [resultIndex, result] of results.entries()) {
|
|
467
576
|
const chunkBoundaries = result.boundaries && result.boundaries.length > 0 ? result.boundaries : result.wordBoundary ?? result.wordBoundaries ?? [];
|
|
468
577
|
for (const boundary of chunkBoundaries) {
|
|
469
578
|
const textRange = boundary.textRange ?? result.textRange;
|
|
@@ -473,7 +582,7 @@ function mergeSynthesisResults(results, format) {
|
|
|
473
582
|
...boundary,
|
|
474
583
|
audioOffsetMs: boundary.audioOffsetMs + durationOffset,
|
|
475
584
|
chunkAudioOffsetMs: boundary.chunkAudioOffsetMs ?? boundary.audioOffsetMs,
|
|
476
|
-
...boundary.chunkIndex === void 0 ? { chunkIndex:
|
|
585
|
+
...boundary.chunkIndex === void 0 ? { chunkIndex: resultIndex } : {},
|
|
477
586
|
...boundary.sourceNodePath ? { sourceNodePath: [...boundary.sourceNodePath] } : {},
|
|
478
587
|
...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
|
|
479
588
|
...textRange ? { textRange: { ...textRange } } : {},
|
|
@@ -488,7 +597,7 @@ function mergeSynthesisResults(results, format) {
|
|
|
488
597
|
...viseme,
|
|
489
598
|
audioOffsetMs: viseme.audioOffsetMs + durationOffset,
|
|
490
599
|
chunkAudioOffsetMs: viseme.chunkAudioOffsetMs ?? viseme.audioOffsetMs,
|
|
491
|
-
...viseme.chunkIndex === void 0 ? { chunkIndex:
|
|
600
|
+
...viseme.chunkIndex === void 0 ? { chunkIndex: resultIndex } : {},
|
|
492
601
|
...viseme.sourceNodePath ? { sourceNodePath: [...viseme.sourceNodePath] } : {},
|
|
493
602
|
...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
|
|
494
603
|
...textRange ? { textRange: { ...textRange } } : {},
|
|
@@ -503,7 +612,7 @@ function mergeSynthesisResults(results, format) {
|
|
|
503
612
|
...bookmark,
|
|
504
613
|
audioOffsetMs: bookmark.audioOffsetMs + durationOffset,
|
|
505
614
|
chunkAudioOffsetMs: bookmark.chunkAudioOffsetMs ?? bookmark.audioOffsetMs,
|
|
506
|
-
...bookmark.chunkIndex === void 0 ? { chunkIndex:
|
|
615
|
+
...bookmark.chunkIndex === void 0 ? { chunkIndex: resultIndex } : {},
|
|
507
616
|
...bookmark.sourceNodePath ? { sourceNodePath: [...bookmark.sourceNodePath] } : {},
|
|
508
617
|
...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
|
|
509
618
|
...textRange ? { textRange: { ...textRange } } : {},
|
|
@@ -513,8 +622,9 @@ function mergeSynthesisResults(results, format) {
|
|
|
513
622
|
durationOffset += Math.max(0, result.durationMs);
|
|
514
623
|
}
|
|
515
624
|
return {
|
|
516
|
-
audioData
|
|
625
|
+
audioData,
|
|
517
626
|
durationMs: durationOffset,
|
|
627
|
+
mimeType: resolveMimeType(format),
|
|
518
628
|
...boundaries.length > 0 ? { boundaries, wordBoundary: boundaries, wordBoundaries: boundaries } : {},
|
|
519
629
|
...visemes.length > 0 ? { visemes } : {},
|
|
520
630
|
...bookmarks.length > 0 ? { bookmarks } : {},
|
|
@@ -522,6 +632,27 @@ function mergeSynthesisResults(results, format) {
|
|
|
522
632
|
...results.length === 1 && results[0]?.textRange ? { textRange: { ...results[0].textRange } } : {}
|
|
523
633
|
};
|
|
524
634
|
}
|
|
635
|
+
function mergeSynthesisResults(results, options) {
|
|
636
|
+
const resolvedOptions = typeof options === "string" ? { format: options } : options;
|
|
637
|
+
const format = resolvedOptions?.format;
|
|
638
|
+
if (!format) throw new UnsupportedMergeFormatError("");
|
|
639
|
+
const buffers = results.map((result) => result.audioData);
|
|
640
|
+
if (resolvedOptions.customMerger) {
|
|
641
|
+
return Promise.resolve().then(() => resolvedOptions.customMerger?.(buffers, format)).then((merged) => {
|
|
642
|
+
if (!merged) throw new MergeError("The custom audio merger returned no audio buffer.");
|
|
643
|
+
return createMergedResult(results, merged, format);
|
|
644
|
+
}).catch((error) => {
|
|
645
|
+
if (error instanceof UnsupportedMergeFormatError || error instanceof MergeError) throw error;
|
|
646
|
+
throw new MergeError(`Custom audio merger failed for format "${format}".`, error);
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
try {
|
|
650
|
+
return createMergedResult(results, mergeAudioBuffers(buffers, { format }), format);
|
|
651
|
+
} catch (error) {
|
|
652
|
+
if (error instanceof UnsupportedMergeFormatError || error instanceof MergeError) throw error;
|
|
653
|
+
throw new MergeError(`Audio buffers could not be merged for format "${format}".`, error);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
525
656
|
async function synthesizeSpeech(ssml, config) {
|
|
526
657
|
return (await synthesizeSsml(ssml, config)).audioData;
|
|
527
658
|
}
|
|
@@ -530,37 +661,48 @@ async function synthesizeSpeech(ssml, config) {
|
|
|
530
661
|
var ChunkValidationError = class extends Error {
|
|
531
662
|
constructor(chunkIndex, diagnostics) {
|
|
532
663
|
super(`SSML validation failed for chunk ${chunkIndex}; the Azure Speech API was not called.`);
|
|
533
|
-
this.kind = "
|
|
664
|
+
this.kind = "validation-error";
|
|
534
665
|
this.name = "ChunkValidationError";
|
|
535
666
|
this.chunkIndex = chunkIndex;
|
|
536
667
|
this.diagnostics = diagnostics;
|
|
537
668
|
}
|
|
538
669
|
};
|
|
670
|
+
function failure(error) {
|
|
671
|
+
return { ok: false, success: false, status: error.kind, error };
|
|
672
|
+
}
|
|
539
673
|
async function synthesizeSsmlSafe(client, ssml, options = {}) {
|
|
540
|
-
const validationOptions = options.validation ?? options;
|
|
674
|
+
const validationOptions = withValidationSignal(options.validation ?? options, options.signal);
|
|
541
675
|
const diagnostics = await Promise.resolve(validateAzureSsml2(ssml, validationOptions));
|
|
676
|
+
if (options.signal?.aborted) {
|
|
677
|
+
const error = toSynthesisError(new Error("Speech synthesis was cancelled."));
|
|
678
|
+
return failure(error);
|
|
679
|
+
}
|
|
542
680
|
const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error");
|
|
543
681
|
if (errors.length > 0) {
|
|
544
|
-
return {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
kind: "validation",
|
|
550
|
-
message: "SSML validation failed; the Azure Speech API was not called.",
|
|
551
|
-
diagnostics: errors
|
|
552
|
-
}
|
|
553
|
-
};
|
|
682
|
+
return failure({
|
|
683
|
+
kind: "validation-error",
|
|
684
|
+
message: "SSML validation failed; the Azure Speech API was not called.",
|
|
685
|
+
diagnostics: errors
|
|
686
|
+
});
|
|
554
687
|
}
|
|
555
688
|
try {
|
|
556
|
-
return {
|
|
689
|
+
return {
|
|
690
|
+
ok: true,
|
|
691
|
+
success: true,
|
|
692
|
+
status: "success",
|
|
693
|
+
value: await client.synthesizeSsml(ssml, { signal: options.signal })
|
|
694
|
+
};
|
|
557
695
|
} catch (error) {
|
|
558
|
-
const
|
|
559
|
-
return
|
|
696
|
+
const synthesisError = toSynthesisError(error);
|
|
697
|
+
return failure(synthesisError);
|
|
560
698
|
}
|
|
561
699
|
}
|
|
562
700
|
async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
563
|
-
const validationOptions = options.validation ?? options;
|
|
701
|
+
const validationOptions = withValidationSignal(options.validation ?? options, options.signal);
|
|
702
|
+
if (options.signal?.aborted) {
|
|
703
|
+
const error = toSynthesisError(new Error("Speech synthesis was cancelled."));
|
|
704
|
+
return failure(error);
|
|
705
|
+
}
|
|
564
706
|
const pending = (index, status, error) => {
|
|
565
707
|
options.onProgress?.({
|
|
566
708
|
currentChunk: status === "success" ? index + 1 : index,
|
|
@@ -579,7 +721,10 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
579
721
|
const validations = await Promise.all(
|
|
580
722
|
chunks.map(async (chunk) => {
|
|
581
723
|
const ssml = typeof chunk === "string" ? chunk : chunk.ssml;
|
|
582
|
-
const
|
|
724
|
+
const sourceNodePath = typeof chunk === "string" ? options.sourceNodePath : chunk.sourceNodePath ?? options.sourceNodePath;
|
|
725
|
+
const diagnostics = await Promise.resolve(
|
|
726
|
+
validateAzureSsml2(ssml, { ...validationOptions, ...sourceNodePath ? { sourceNodePath } : {} })
|
|
727
|
+
);
|
|
583
728
|
return diagnostics.filter((diagnostic) => diagnostic.severity === "error");
|
|
584
729
|
})
|
|
585
730
|
);
|
|
@@ -587,31 +732,78 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
587
732
|
if (firstInvalidIndex >= 0) {
|
|
588
733
|
const error = new ChunkValidationError(firstInvalidIndex, validations[firstInvalidIndex] ?? []);
|
|
589
734
|
pending(firstInvalidIndex, "failed", error);
|
|
590
|
-
return
|
|
735
|
+
return failure(error);
|
|
591
736
|
}
|
|
592
737
|
try {
|
|
593
738
|
if (client.synthesizeChunks) {
|
|
594
|
-
const
|
|
739
|
+
const normalizedChunks = chunks.map((chunk) => {
|
|
740
|
+
if (typeof chunk === "string" || chunk.sourceNodePath || !options.sourceNodePath) return chunk;
|
|
741
|
+
return { ...chunk, sourceNodePath: [...options.sourceNodePath] };
|
|
742
|
+
});
|
|
743
|
+
const value = await client.synthesizeChunks(normalizedChunks, {
|
|
744
|
+
onProgress: options.onProgress,
|
|
745
|
+
outputFormat: options.outputFormat,
|
|
746
|
+
signal: options.signal,
|
|
747
|
+
timeoutMs: options.timeoutMs,
|
|
748
|
+
sourceNodePath: options.sourceNodePath
|
|
749
|
+
});
|
|
595
750
|
return { ok: true, success: true, status: "success", value };
|
|
596
751
|
}
|
|
597
752
|
const results = [];
|
|
598
753
|
for (const [index, chunk] of chunks.entries()) {
|
|
599
754
|
const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
|
|
600
755
|
const sourceNodePath = input.sourceNodePath;
|
|
756
|
+
const originalTextRange = input.originalTextRange;
|
|
601
757
|
pending(index, "synthesizing");
|
|
602
758
|
const startedAt = Date.now();
|
|
603
759
|
try {
|
|
604
|
-
const result = await client.synthesizeSsml(input.ssml
|
|
760
|
+
const result = await client.synthesizeSsml(input.ssml, {
|
|
761
|
+
outputFormat: options.outputFormat,
|
|
762
|
+
signal: options.signal,
|
|
763
|
+
timeoutMs: options.timeoutMs,
|
|
764
|
+
sourceNodePath: input.sourceNodePath ?? options.sourceNodePath
|
|
765
|
+
});
|
|
605
766
|
results.push({
|
|
606
767
|
...result,
|
|
607
768
|
...input.originalTextRange ? { textRange: { ...input.originalTextRange } } : {},
|
|
608
769
|
...sourceNodePath ? {
|
|
609
770
|
boundaries: result.boundaries?.map((event) => ({
|
|
610
771
|
...event,
|
|
611
|
-
sourceNodePath: [...sourceNodePath]
|
|
772
|
+
sourceNodePath: [...sourceNodePath],
|
|
773
|
+
...event.originalTextRange ? { originalTextRange: { ...event.originalTextRange } } : input.originalTextRange ? { originalTextRange: { ...input.originalTextRange } } : {}
|
|
774
|
+
})),
|
|
775
|
+
visemes: result.visemes?.map((event) => ({
|
|
776
|
+
...event,
|
|
777
|
+
sourceNodePath: [...sourceNodePath],
|
|
778
|
+
...event.originalTextRange ? { originalTextRange: { ...event.originalTextRange } } : input.originalTextRange ? { originalTextRange: { ...input.originalTextRange } } : {}
|
|
779
|
+
})),
|
|
780
|
+
bookmarks: result.bookmarks?.map((event) => ({
|
|
781
|
+
...event,
|
|
782
|
+
sourceNodePath: [...sourceNodePath],
|
|
783
|
+
...event.originalTextRange ? { originalTextRange: { ...event.originalTextRange } } : input.originalTextRange ? { originalTextRange: { ...input.originalTextRange } } : {}
|
|
784
|
+
}))
|
|
785
|
+
} : {},
|
|
786
|
+
...originalTextRange ? {
|
|
787
|
+
boundaries: result.boundaries?.map((event) => ({
|
|
788
|
+
...event,
|
|
789
|
+
originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
|
|
612
790
|
})),
|
|
613
|
-
|
|
614
|
-
|
|
791
|
+
wordBoundary: result.wordBoundary?.map((event) => ({
|
|
792
|
+
...event,
|
|
793
|
+
originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
|
|
794
|
+
})),
|
|
795
|
+
wordBoundaries: result.wordBoundaries?.map((event) => ({
|
|
796
|
+
...event,
|
|
797
|
+
originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
|
|
798
|
+
})),
|
|
799
|
+
visemes: result.visemes?.map((event) => ({
|
|
800
|
+
...event,
|
|
801
|
+
originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
|
|
802
|
+
})),
|
|
803
|
+
bookmarks: result.bookmarks?.map((event) => ({
|
|
804
|
+
...event,
|
|
805
|
+
originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
|
|
806
|
+
}))
|
|
615
807
|
} : {}
|
|
616
808
|
});
|
|
617
809
|
options.onProgress?.({
|
|
@@ -641,13 +833,23 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
641
833
|
ok: true,
|
|
642
834
|
success: true,
|
|
643
835
|
status: "success",
|
|
644
|
-
value: mergeSynthesisResults(results,
|
|
836
|
+
value: mergeSynthesisResults(results, {
|
|
837
|
+
format: options.outputFormat ?? "audio-16khz-128kbitrate-mono-mp3"
|
|
838
|
+
})
|
|
645
839
|
};
|
|
646
840
|
} catch (error) {
|
|
647
|
-
const
|
|
648
|
-
return
|
|
841
|
+
const synthesisError = toSynthesisError(error);
|
|
842
|
+
return failure(synthesisError);
|
|
649
843
|
}
|
|
650
844
|
}
|
|
845
|
+
function withValidationSignal(options, signal) {
|
|
846
|
+
if (!signal) return options;
|
|
847
|
+
return {
|
|
848
|
+
...options,
|
|
849
|
+
urlValidatorSignal: signal,
|
|
850
|
+
urlValidation: { ...options.urlValidation ?? {}, signal }
|
|
851
|
+
};
|
|
852
|
+
}
|
|
651
853
|
|
|
652
854
|
// packages/azure-tts-client/src/client.ts
|
|
653
855
|
var ENDPOINT_TEMPLATE = "https://{region}.tts.speech.microsoft.com/cognitiveservices/v1";
|
|
@@ -664,11 +866,21 @@ var AzureTtsClient = class {
|
|
|
664
866
|
const config = { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs };
|
|
665
867
|
return synthesizeSpeech(ssml, config);
|
|
666
868
|
}
|
|
667
|
-
async synthesizeSsml(ssml) {
|
|
869
|
+
async synthesizeSsml(ssml, options = {}) {
|
|
668
870
|
const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
|
|
669
871
|
const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
670
872
|
__privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
|
|
671
|
-
return synthesizeSsml(ssml, {
|
|
873
|
+
return synthesizeSsml(ssml, {
|
|
874
|
+
endpoint,
|
|
875
|
+
region,
|
|
876
|
+
subscriptionKey,
|
|
877
|
+
outputFormat: options.outputFormat ?? outputFormat,
|
|
878
|
+
signal: options.signal ?? signal,
|
|
879
|
+
timeoutMs: options.timeoutMs ?? timeoutMs,
|
|
880
|
+
sourceNodePath: options.sourceNodePath,
|
|
881
|
+
sourceTextSegments: options.sourceTextSegments,
|
|
882
|
+
sourceMarkers: options.sourceMarkers
|
|
883
|
+
});
|
|
672
884
|
}
|
|
673
885
|
async synthesizeChunks(chunks, options = {}) {
|
|
674
886
|
const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
|
|
@@ -677,9 +889,10 @@ var AzureTtsClient = class {
|
|
|
677
889
|
endpoint,
|
|
678
890
|
region,
|
|
679
891
|
subscriptionKey,
|
|
680
|
-
outputFormat,
|
|
681
|
-
signal,
|
|
682
|
-
timeoutMs,
|
|
892
|
+
outputFormat: options.outputFormat ?? outputFormat,
|
|
893
|
+
signal: options.signal ?? signal,
|
|
894
|
+
timeoutMs: options.timeoutMs ?? timeoutMs,
|
|
895
|
+
sourceNodePath: options.sourceNodePath,
|
|
683
896
|
onProgress: options.onProgress ?? __privateGet(this, _options).onProgress
|
|
684
897
|
});
|
|
685
898
|
}
|
|
@@ -690,6 +903,8 @@ var AzureTtsClient = class {
|
|
|
690
903
|
return synthesizeSsmlChunksSafe(this, chunks, {
|
|
691
904
|
...options,
|
|
692
905
|
outputFormat: options.outputFormat ?? __privateGet(this, _options).outputFormat,
|
|
906
|
+
signal: options.signal ?? __privateGet(this, _options).signal,
|
|
907
|
+
timeoutMs: options.timeoutMs ?? __privateGet(this, _options).timeoutMs,
|
|
693
908
|
onProgress: options.onProgress ?? __privateGet(this, _options).onProgress
|
|
694
909
|
});
|
|
695
910
|
}
|
|
@@ -790,6 +1005,10 @@ export {
|
|
|
790
1005
|
AzureTtsError,
|
|
791
1006
|
AzureTtsSdkError,
|
|
792
1007
|
ChunkValidationError,
|
|
1008
|
+
DEFAULT_OUTPUT_FORMAT,
|
|
1009
|
+
MergeError,
|
|
1010
|
+
SynthesisCancelledError,
|
|
1011
|
+
SynthesisTimeoutError,
|
|
793
1012
|
UnsupportedMergeFormatError,
|
|
794
1013
|
areAzureLanguagesEquivalent,
|
|
795
1014
|
buildPartialSsml,
|
|
@@ -802,6 +1021,7 @@ export {
|
|
|
802
1021
|
fromPlainTextToSsml,
|
|
803
1022
|
getAzureVoiceCatalogMetadata,
|
|
804
1023
|
getBuiltInVoiceCatalogMetadata,
|
|
1024
|
+
getSsmlSourceMap,
|
|
805
1025
|
isValidAzureAudioDuration,
|
|
806
1026
|
mapSsmlTextNodes,
|
|
807
1027
|
mergeAudioBuffers,
|
|
@@ -809,6 +1029,7 @@ export {
|
|
|
809
1029
|
normalizeAzureLanguage,
|
|
810
1030
|
parseSsml,
|
|
811
1031
|
resolveMergeAudioFormat,
|
|
1032
|
+
resolveMimeType,
|
|
812
1033
|
splitSsmlDocument,
|
|
813
1034
|
synthesizeSpeech,
|
|
814
1035
|
synthesizeSsml,
|