ssml-builder-js 2.15.0 → 2.17.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 +11 -3
- package/dist/{chunk-FXUM45ZY.mjs → chunk-2SYLELUT.mjs} +53 -6
- package/dist/chunk-2SYLELUT.mjs.map +1 -0
- package/dist/{chunk-WXFLUCLR.mjs → chunk-5AZWURHW.mjs} +37 -6
- package/dist/chunk-5AZWURHW.mjs.map +1 -0
- package/dist/{chunk-BDY2Q2JL.mjs → chunk-SLZ7PE6W.mjs} +2 -2
- package/dist/core.d.mts +24 -1
- package/dist/core.d.ts +24 -1
- package/dist/core.js +53 -5
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +3 -1
- package/dist/elements.js +35 -5
- package/dist/elements.js.map +1 -1
- package/dist/elements.mjs +2 -2
- package/dist/{index.d-8BvkB9gz.d.mts → index.d-tpKoP1jl.d.mts} +27 -1
- package/dist/{index.d-8BvkB9gz.d.ts → index.d-tpKoP1jl.d.ts} +27 -1
- package/dist/index.d.mts +123 -9
- package/dist/index.d.ts +123 -9
- package/dist/index.js +874 -180
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +787 -172
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +5 -2
- package/dist/react.d.ts +5 -2
- package/dist/react.js +143 -29
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +110 -26
- package/dist/react.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-FXUM45ZY.mjs.map +0 -1
- package/dist/chunk-WXFLUCLR.mjs.map +0 -1
- /package/dist/{chunk-BDY2Q2JL.mjs.map → chunk-SLZ7PE6W.mjs.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -37,9 +37,11 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
37
37
|
// src/index.ts
|
|
38
38
|
var src_exports = {};
|
|
39
39
|
__export(src_exports, {
|
|
40
|
+
AudioFormatMismatchError: () => AudioFormatMismatchError,
|
|
40
41
|
AzureTtsClient: () => AzureTtsClient,
|
|
41
42
|
AzureTtsError: () => AzureTtsError,
|
|
42
43
|
AzureTtsSdkError: () => AzureTtsSdkError,
|
|
44
|
+
BatchChunkValidationError: () => BatchChunkValidationError,
|
|
43
45
|
ChunkValidationError: () => ChunkValidationError,
|
|
44
46
|
DEFAULT_OUTPUT_FORMAT: () => DEFAULT_OUTPUT_FORMAT,
|
|
45
47
|
MergeError: () => MergeError,
|
|
@@ -57,7 +59,9 @@ __export(src_exports, {
|
|
|
57
59
|
fromPlainTextToSsml: () => fromPlainTextToSsml,
|
|
58
60
|
getAzureVoiceCatalogMetadata: () => getAzureVoiceCatalogMetadata,
|
|
59
61
|
getBuiltInVoiceCatalogMetadata: () => getBuiltInVoiceCatalogMetadata,
|
|
62
|
+
getRetryAfterDelayMs: () => getRetryAfterDelayMs,
|
|
60
63
|
getSsmlSourceMap: () => getSsmlSourceMap,
|
|
64
|
+
inspectAudioSpecification: () => inspectAudioSpecification,
|
|
61
65
|
isValidAzureAudioDuration: () => isValidAzureAudioDuration,
|
|
62
66
|
mapSsmlTextNodes: () => mapSsmlTextNodes,
|
|
63
67
|
mergeAudioBuffers: () => mergeAudioBuffers,
|
|
@@ -73,6 +77,7 @@ __export(src_exports, {
|
|
|
73
77
|
synthesizeSsmlChunksSafe: () => synthesizeSsmlChunksSafe,
|
|
74
78
|
synthesizeSsmlSafe: () => synthesizeSsmlSafe,
|
|
75
79
|
validateAzureSsml: () => validateAzureSsml,
|
|
80
|
+
validateAzureSsmlChunks: () => validateAzureSsmlChunks,
|
|
76
81
|
validateSsml: () => validateSsml,
|
|
77
82
|
validateSsmlStructureIntegrity: () => validateSsmlStructureIntegrity
|
|
78
83
|
});
|
|
@@ -2089,6 +2094,7 @@ function tokenizeElements(source) {
|
|
|
2089
2094
|
if (parent) parent.childElementCount += 1;
|
|
2090
2095
|
const parentVoiceName = [...openElements].reverse().find((element) => element.voiceName)?.voiceName;
|
|
2091
2096
|
const tokenName = nameMatch[1];
|
|
2097
|
+
const path = parent ? [...parent.path, `${tokenName}[${childElementIndex ?? 0}]`] : [tokenName];
|
|
2092
2098
|
const tokenVoiceName = tokenName.toLowerCase() === "voice" ? attributes.get("name") : tokenName.toLowerCase() === "mstts:turn" ? attributes.get("voice") ?? parentVoiceName : parentVoiceName;
|
|
2093
2099
|
tokens.push({
|
|
2094
2100
|
attributes,
|
|
@@ -2099,12 +2105,14 @@ function tokenizeElements(source) {
|
|
|
2099
2105
|
parentName: parent?.name,
|
|
2100
2106
|
parentVoiceName,
|
|
2101
2107
|
selfClosing,
|
|
2102
|
-
start
|
|
2108
|
+
start,
|
|
2109
|
+
path
|
|
2103
2110
|
});
|
|
2104
2111
|
if (!selfClosing) {
|
|
2105
2112
|
openElements.push({
|
|
2106
2113
|
childElementCount: 0,
|
|
2107
2114
|
name: tokenName,
|
|
2115
|
+
path,
|
|
2108
2116
|
voiceName: tokenVoiceName
|
|
2109
2117
|
});
|
|
2110
2118
|
}
|
|
@@ -2117,13 +2125,14 @@ function location(source, offset) {
|
|
|
2117
2125
|
const line = before.split("\n").length;
|
|
2118
2126
|
return { line, column: before.length - (before.lastIndexOf("\n") + 1) + 1 };
|
|
2119
2127
|
}
|
|
2120
|
-
function addDiagnostic(diagnostics, source, offset, message, severity = "error", code) {
|
|
2128
|
+
function addDiagnostic(diagnostics, source, offset, message, severity = "error", code, metadata = {}) {
|
|
2121
2129
|
diagnostics.push({
|
|
2122
2130
|
...location(source, offset),
|
|
2123
2131
|
message,
|
|
2124
2132
|
severity,
|
|
2125
2133
|
source: "ssml-static-validator",
|
|
2126
|
-
...code ? { code } : {}
|
|
2134
|
+
...code ? { code } : {},
|
|
2135
|
+
...metadata
|
|
2127
2136
|
});
|
|
2128
2137
|
}
|
|
2129
2138
|
function isSupportedProsodyRate(value) {
|
|
@@ -2596,9 +2605,11 @@ function validateAzureSsmlStatic(ssml, options = {}) {
|
|
|
2596
2605
|
for (const token of tokens) {
|
|
2597
2606
|
const tokenName = token.name.toLowerCase();
|
|
2598
2607
|
const tokenVoiceName = tokenName === "voice" ? attr(token, "name")?.trim() : tokenName === "mstts:turn" ? attr(token, "voice")?.trim() || token.parentVoiceName : options.validateNestedVoices === false ? voiceName : token.parentVoiceName;
|
|
2608
|
+
const tokenDiagnosticStart = diagnostics.length;
|
|
2599
2609
|
validateElement(token, ssml, diagnostics, tokenVoiceName, options, voiceCatalog);
|
|
2600
2610
|
const definition = tokenVoiceName ? voiceCatalog.get(tokenVoiceName.toLowerCase()) : void 0;
|
|
2601
2611
|
validateVoiceFeatureMatrix(token, ssml, diagnostics, tokenVoiceName, definition);
|
|
2612
|
+
annotateTokenDiagnostics(diagnostics, tokenDiagnosticStart, token, options, tokenVoiceName);
|
|
2602
2613
|
if (tokenName === "voice" && options.model && definition?.models && !definition.models.some((model) => model.toLowerCase() === options.model?.toLowerCase())) {
|
|
2603
2614
|
addDiagnostic(
|
|
2604
2615
|
diagnostics,
|
|
@@ -2620,12 +2631,30 @@ function urlAttributes(token) {
|
|
|
2620
2631
|
return value === void 0 ? [] : [{ attribute, value }];
|
|
2621
2632
|
});
|
|
2622
2633
|
}
|
|
2634
|
+
function annotateTokenDiagnostics(diagnostics, startIndex, token, options, voiceName) {
|
|
2635
|
+
const attributes = [...token.attributes.keys()];
|
|
2636
|
+
for (const diagnostic of diagnostics.slice(startIndex)) {
|
|
2637
|
+
const attributeName = attributes.find(
|
|
2638
|
+
(attribute) => new RegExp(`(?:<[^> ]+\\s+|")${attribute}(?:"|>|\\s)`, "i").test(diagnostic.message)
|
|
2639
|
+
);
|
|
2640
|
+
const nodePath = options.sourceNodePath ? [...options.sourceNodePath] : [...token.path];
|
|
2641
|
+
Object.assign(diagnostic, {
|
|
2642
|
+
range: { start: token.start, end: token.end + 1 },
|
|
2643
|
+
tagName: token.name,
|
|
2644
|
+
...attributeName ? { attributeName } : {},
|
|
2645
|
+
...voiceName ? { voiceName } : {},
|
|
2646
|
+
...options.chunkIndex !== void 0 ? { chunkIndex: options.chunkIndex } : {},
|
|
2647
|
+
nodePath,
|
|
2648
|
+
targetNodePath: [...token.path]
|
|
2649
|
+
});
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2623
2652
|
function validateAzureSsml(ssml, options = {}) {
|
|
2624
2653
|
const diagnostics = validateAzureSsmlStatic(ssml, options);
|
|
2625
2654
|
const validator = options.urlValidator ?? options.customUrlValidator;
|
|
2626
2655
|
if (!validator || typeof ssml !== "string") return diagnostics;
|
|
2627
2656
|
const runnerOptions = options.urlValidation ?? {};
|
|
2628
|
-
const boundedValidator = createAzureUrlValidatorRunner(validator, {
|
|
2657
|
+
const boundedValidator = options.urlValidatorRunner ?? createAzureUrlValidatorRunner(validator, {
|
|
2629
2658
|
...runnerOptions,
|
|
2630
2659
|
...options.urlValidatorConcurrency !== void 0 ? { concurrency: options.urlValidatorConcurrency } : {},
|
|
2631
2660
|
...options.urlValidatorTimeoutMs !== void 0 ? { timeoutMs: options.urlValidatorTimeoutMs } : {},
|
|
@@ -2650,33 +2679,55 @@ function validateAzureSsml(ssml, options = {}) {
|
|
|
2650
2679
|
const valid = typeof result === "boolean" ? result : result.valid;
|
|
2651
2680
|
if (!valid) {
|
|
2652
2681
|
const reason = typeof result === "boolean" ? void 0 : result.reason;
|
|
2682
|
+
const diagnosticStart = diagnostics.length;
|
|
2653
2683
|
addDiagnostic(
|
|
2654
2684
|
diagnostics,
|
|
2655
2685
|
ssml,
|
|
2656
2686
|
token.start,
|
|
2657
2687
|
`<${token.name} ${attribute}> was rejected by the custom URL validator${reason ? `: ${reason}` : "."}`
|
|
2658
2688
|
);
|
|
2689
|
+
annotateTokenDiagnostics(diagnostics, diagnosticStart, token, options, token.parentVoiceName);
|
|
2659
2690
|
}
|
|
2660
2691
|
} catch (error) {
|
|
2661
2692
|
const reason = error instanceof Error ? error.message : String(error);
|
|
2693
|
+
const diagnosticStart = diagnostics.length;
|
|
2662
2694
|
addDiagnostic(
|
|
2663
2695
|
diagnostics,
|
|
2664
2696
|
ssml,
|
|
2665
2697
|
token.start,
|
|
2666
2698
|
`<${token.name} ${attribute}> could not be validated by the custom URL validator: ${reason}`
|
|
2667
2699
|
);
|
|
2700
|
+
annotateTokenDiagnostics(diagnostics, diagnosticStart, token, options, token.parentVoiceName);
|
|
2668
2701
|
}
|
|
2669
2702
|
})
|
|
2670
2703
|
);
|
|
2671
2704
|
return Promise.all(checks).then(() => diagnostics);
|
|
2672
2705
|
}
|
|
2706
|
+
async function validateAzureSsmlChunks(chunks, options = {}) {
|
|
2707
|
+
const validator = options.urlValidator ?? options.customUrlValidator;
|
|
2708
|
+
const sharedOptions = validator ? {
|
|
2709
|
+
...options,
|
|
2710
|
+
urlValidatorRunner: options.urlValidatorRunner ?? createAzureUrlValidatorRunner(validator, {
|
|
2711
|
+
...options.urlValidation ?? {},
|
|
2712
|
+
...options.urlValidatorConcurrency !== void 0 ? { concurrency: options.urlValidatorConcurrency } : {},
|
|
2713
|
+
...options.urlValidatorTimeoutMs !== void 0 ? { timeoutMs: options.urlValidatorTimeoutMs } : {},
|
|
2714
|
+
...options.urlValidatorSignal ? { signal: options.urlValidatorSignal } : {},
|
|
2715
|
+
...options.urlValidatorCache ? { cache: options.urlValidatorCache } : {}
|
|
2716
|
+
})
|
|
2717
|
+
} : options;
|
|
2718
|
+
return Promise.all(
|
|
2719
|
+
chunks.map((chunk, chunkIndex) => Promise.resolve(validateAzureSsml(chunk, { ...sharedOptions, chunkIndex })))
|
|
2720
|
+
);
|
|
2721
|
+
}
|
|
2673
2722
|
|
|
2674
2723
|
// packages/ssml-core/src/generated/azureVoiceCatalog.ts
|
|
2675
2724
|
var AZURE_VOICE_CATALOG_METADATA = {
|
|
2676
2725
|
apiVersion: "2025-10-01",
|
|
2677
2726
|
generatedAt: "2026-08-28T00:00:00.000Z",
|
|
2678
2727
|
regions: [],
|
|
2679
|
-
voiceCount: AZURE_VOICE_DEFINITIONS.length
|
|
2728
|
+
voiceCount: AZURE_VOICE_DEFINITIONS.length,
|
|
2729
|
+
expiresAt: "2026-09-04T00:00:00.000Z",
|
|
2730
|
+
regionDiffs: {}
|
|
2680
2731
|
};
|
|
2681
2732
|
|
|
2682
2733
|
// packages/ssml-core/src/voiceCatalog.ts
|
|
@@ -2690,7 +2741,7 @@ var getBuiltInVoiceCatalogMetadata = getAzureVoiceCatalogMetadata;
|
|
|
2690
2741
|
|
|
2691
2742
|
// packages/azure-tts-client/src/errors.ts
|
|
2692
2743
|
var AzureTtsError = class extends Error {
|
|
2693
|
-
constructor(status, statusText, responseBody, requestId) {
|
|
2744
|
+
constructor(status, statusText, responseBody, requestId, responseHeaders) {
|
|
2694
2745
|
super(`Azure TTS request failed: ${status} ${statusText}`);
|
|
2695
2746
|
this.kind = "azure-api-error";
|
|
2696
2747
|
this.name = "AzureTtsError";
|
|
@@ -2698,8 +2749,37 @@ var AzureTtsError = class extends Error {
|
|
|
2698
2749
|
this.statusText = statusText;
|
|
2699
2750
|
this.responseBody = responseBody;
|
|
2700
2751
|
this.requestId = requestId;
|
|
2752
|
+
const value = responseHeaders instanceof Headers ? responseHeaders.get("retry-after") : responseHeaders?.["retry-after"] ?? responseHeaders?.["Retry-After"];
|
|
2753
|
+
const seconds = value ? Number(value.trim()) : NaN;
|
|
2754
|
+
const date = value ? Date.parse(value) : NaN;
|
|
2755
|
+
if (Number.isFinite(seconds) && seconds >= 0) this.retryAfterMs = seconds * 1e3;
|
|
2756
|
+
else if (Number.isFinite(date)) this.retryAfterMs = Math.max(0, date - Date.now());
|
|
2701
2757
|
}
|
|
2702
2758
|
};
|
|
2759
|
+
function getRetryAfterDelayMs(error) {
|
|
2760
|
+
if (error instanceof AzureTtsError && error.retryAfterMs !== void 0) return error.retryAfterMs;
|
|
2761
|
+
if (!error || typeof error !== "object") return void 0;
|
|
2762
|
+
const candidate = error;
|
|
2763
|
+
if (typeof candidate.retryAfterMs === "number" && candidate.retryAfterMs >= 0) return candidate.retryAfterMs;
|
|
2764
|
+
const headers = candidate.headers ?? candidate.response?.headers;
|
|
2765
|
+
if (headers instanceof Headers) {
|
|
2766
|
+
const value = headers.get("retry-after");
|
|
2767
|
+
if (!value) return void 0;
|
|
2768
|
+
const seconds = Number(value.trim());
|
|
2769
|
+
if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
|
|
2770
|
+
const date = Date.parse(value);
|
|
2771
|
+
return Number.isFinite(date) ? Math.max(0, date - Date.now()) : void 0;
|
|
2772
|
+
}
|
|
2773
|
+
if (headers && typeof headers === "object") {
|
|
2774
|
+
const value = headers["retry-after"] ?? headers["Retry-After"];
|
|
2775
|
+
if (typeof value !== "string") return void 0;
|
|
2776
|
+
const seconds = Number(value.trim());
|
|
2777
|
+
if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
|
|
2778
|
+
const date = Date.parse(value);
|
|
2779
|
+
return Number.isFinite(date) ? Math.max(0, date - Date.now()) : void 0;
|
|
2780
|
+
}
|
|
2781
|
+
return void 0;
|
|
2782
|
+
}
|
|
2703
2783
|
var AzureTtsSdkError = class extends AzureTtsError {
|
|
2704
2784
|
constructor(errorDetails) {
|
|
2705
2785
|
super(0, "Speech SDK", errorDetails, null);
|
|
@@ -2730,6 +2810,14 @@ var MergeError = class extends Error {
|
|
|
2730
2810
|
this.cause = cause;
|
|
2731
2811
|
}
|
|
2732
2812
|
};
|
|
2813
|
+
var AudioFormatMismatchError = class extends Error {
|
|
2814
|
+
constructor(message, inputSpecs = []) {
|
|
2815
|
+
super(message);
|
|
2816
|
+
this.kind = "audio-format-mismatch";
|
|
2817
|
+
this.name = "AudioFormatMismatchError";
|
|
2818
|
+
this.inputSpecs = inputSpecs;
|
|
2819
|
+
}
|
|
2820
|
+
};
|
|
2733
2821
|
var UnsupportedMergeFormatError = class extends Error {
|
|
2734
2822
|
constructor(format) {
|
|
2735
2823
|
super(`Audio format "${format}" cannot be safely concatenated; container re-multiplexing is required.`);
|
|
@@ -2739,7 +2827,7 @@ var UnsupportedMergeFormatError = class extends Error {
|
|
|
2739
2827
|
}
|
|
2740
2828
|
};
|
|
2741
2829
|
function toSynthesisError(error) {
|
|
2742
|
-
if (error instanceof AzureTtsError || error instanceof MergeError || error instanceof UnsupportedMergeFormatError || error instanceof SynthesisCancelledError || error instanceof SynthesisTimeoutError)
|
|
2830
|
+
if (error instanceof AzureTtsError || error instanceof MergeError || error instanceof AudioFormatMismatchError || error instanceof UnsupportedMergeFormatError || error instanceof SynthesisCancelledError || error instanceof SynthesisTimeoutError)
|
|
2743
2831
|
return error;
|
|
2744
2832
|
const message = error instanceof Error ? error.message : String(error);
|
|
2745
2833
|
if (/cancel|abort/i.test(message)) return new SynthesisCancelledError(message);
|
|
@@ -3840,6 +3928,7 @@ function tokenizeElements2(source) {
|
|
|
3840
3928
|
if (parent) parent.childElementCount += 1;
|
|
3841
3929
|
const parentVoiceName = [...openElements].reverse().find((element) => element.voiceName)?.voiceName;
|
|
3842
3930
|
const tokenName = nameMatch[1];
|
|
3931
|
+
const path = parent ? [...parent.path, `${tokenName}[${childElementIndex ?? 0}]`] : [tokenName];
|
|
3843
3932
|
const tokenVoiceName = tokenName.toLowerCase() === "voice" ? attributes.get("name") : tokenName.toLowerCase() === "mstts:turn" ? attributes.get("voice") ?? parentVoiceName : parentVoiceName;
|
|
3844
3933
|
tokens.push({
|
|
3845
3934
|
attributes,
|
|
@@ -3850,12 +3939,14 @@ function tokenizeElements2(source) {
|
|
|
3850
3939
|
parentName: parent?.name,
|
|
3851
3940
|
parentVoiceName,
|
|
3852
3941
|
selfClosing,
|
|
3853
|
-
start
|
|
3942
|
+
start,
|
|
3943
|
+
path
|
|
3854
3944
|
});
|
|
3855
3945
|
if (!selfClosing) {
|
|
3856
3946
|
openElements.push({
|
|
3857
3947
|
childElementCount: 0,
|
|
3858
3948
|
name: tokenName,
|
|
3949
|
+
path,
|
|
3859
3950
|
voiceName: tokenVoiceName
|
|
3860
3951
|
});
|
|
3861
3952
|
}
|
|
@@ -3868,13 +3959,14 @@ function location2(source, offset) {
|
|
|
3868
3959
|
const line = before.split("\n").length;
|
|
3869
3960
|
return { line, column: before.length - (before.lastIndexOf("\n") + 1) + 1 };
|
|
3870
3961
|
}
|
|
3871
|
-
function addDiagnostic2(diagnostics, source, offset, message, severity = "error", code) {
|
|
3962
|
+
function addDiagnostic2(diagnostics, source, offset, message, severity = "error", code, metadata = {}) {
|
|
3872
3963
|
diagnostics.push({
|
|
3873
3964
|
...location2(source, offset),
|
|
3874
3965
|
message,
|
|
3875
3966
|
severity,
|
|
3876
3967
|
source: "ssml-static-validator",
|
|
3877
|
-
...code ? { code } : {}
|
|
3968
|
+
...code ? { code } : {},
|
|
3969
|
+
...metadata
|
|
3878
3970
|
});
|
|
3879
3971
|
}
|
|
3880
3972
|
function isSupportedProsodyRate2(value) {
|
|
@@ -4337,9 +4429,11 @@ function validateAzureSsmlStatic2(ssml, options = {}) {
|
|
|
4337
4429
|
for (const token of tokens) {
|
|
4338
4430
|
const tokenName = token.name.toLowerCase();
|
|
4339
4431
|
const tokenVoiceName = tokenName === "voice" ? attr2(token, "name")?.trim() : tokenName === "mstts:turn" ? attr2(token, "voice")?.trim() || token.parentVoiceName : options.validateNestedVoices === false ? voiceName : token.parentVoiceName;
|
|
4432
|
+
const tokenDiagnosticStart = diagnostics.length;
|
|
4340
4433
|
validateElement2(token, ssml, diagnostics, tokenVoiceName, options, voiceCatalog);
|
|
4341
4434
|
const definition = tokenVoiceName ? voiceCatalog.get(tokenVoiceName.toLowerCase()) : void 0;
|
|
4342
4435
|
validateVoiceFeatureMatrix2(token, ssml, diagnostics, tokenVoiceName, definition);
|
|
4436
|
+
annotateTokenDiagnostics2(diagnostics, tokenDiagnosticStart, token, options, tokenVoiceName);
|
|
4343
4437
|
if (tokenName === "voice" && options.model && definition?.models && !definition.models.some((model) => model.toLowerCase() === options.model?.toLowerCase())) {
|
|
4344
4438
|
addDiagnostic2(
|
|
4345
4439
|
diagnostics,
|
|
@@ -4361,12 +4455,30 @@ function urlAttributes2(token) {
|
|
|
4361
4455
|
return value === void 0 ? [] : [{ attribute, value }];
|
|
4362
4456
|
});
|
|
4363
4457
|
}
|
|
4458
|
+
function annotateTokenDiagnostics2(diagnostics, startIndex, token, options, voiceName) {
|
|
4459
|
+
const attributes = [...token.attributes.keys()];
|
|
4460
|
+
for (const diagnostic of diagnostics.slice(startIndex)) {
|
|
4461
|
+
const attributeName = attributes.find(
|
|
4462
|
+
(attribute) => new RegExp(`(?:<[^> ]+\\s+|")${attribute}(?:"|>|\\s)`, "i").test(diagnostic.message)
|
|
4463
|
+
);
|
|
4464
|
+
const nodePath = options.sourceNodePath ? [...options.sourceNodePath] : [...token.path];
|
|
4465
|
+
Object.assign(diagnostic, {
|
|
4466
|
+
range: { start: token.start, end: token.end + 1 },
|
|
4467
|
+
tagName: token.name,
|
|
4468
|
+
...attributeName ? { attributeName } : {},
|
|
4469
|
+
...voiceName ? { voiceName } : {},
|
|
4470
|
+
...options.chunkIndex !== void 0 ? { chunkIndex: options.chunkIndex } : {},
|
|
4471
|
+
nodePath,
|
|
4472
|
+
targetNodePath: [...token.path]
|
|
4473
|
+
});
|
|
4474
|
+
}
|
|
4475
|
+
}
|
|
4364
4476
|
function validateAzureSsml2(ssml, options = {}) {
|
|
4365
4477
|
const diagnostics = validateAzureSsmlStatic2(ssml, options);
|
|
4366
4478
|
const validator = options.urlValidator ?? options.customUrlValidator;
|
|
4367
4479
|
if (!validator || typeof ssml !== "string") return diagnostics;
|
|
4368
4480
|
const runnerOptions = options.urlValidation ?? {};
|
|
4369
|
-
const boundedValidator = createAzureUrlValidatorRunner2(validator, {
|
|
4481
|
+
const boundedValidator = options.urlValidatorRunner ?? createAzureUrlValidatorRunner2(validator, {
|
|
4370
4482
|
...runnerOptions,
|
|
4371
4483
|
...options.urlValidatorConcurrency !== void 0 ? { concurrency: options.urlValidatorConcurrency } : {},
|
|
4372
4484
|
...options.urlValidatorTimeoutMs !== void 0 ? { timeoutMs: options.urlValidatorTimeoutMs } : {},
|
|
@@ -4391,21 +4503,25 @@ function validateAzureSsml2(ssml, options = {}) {
|
|
|
4391
4503
|
const valid = typeof result === "boolean" ? result : result.valid;
|
|
4392
4504
|
if (!valid) {
|
|
4393
4505
|
const reason = typeof result === "boolean" ? void 0 : result.reason;
|
|
4506
|
+
const diagnosticStart = diagnostics.length;
|
|
4394
4507
|
addDiagnostic2(
|
|
4395
4508
|
diagnostics,
|
|
4396
4509
|
ssml,
|
|
4397
4510
|
token.start,
|
|
4398
4511
|
`<${token.name} ${attribute}> was rejected by the custom URL validator${reason ? `: ${reason}` : "."}`
|
|
4399
4512
|
);
|
|
4513
|
+
annotateTokenDiagnostics2(diagnostics, diagnosticStart, token, options, token.parentVoiceName);
|
|
4400
4514
|
}
|
|
4401
4515
|
} catch (error) {
|
|
4402
4516
|
const reason = error instanceof Error ? error.message : String(error);
|
|
4517
|
+
const diagnosticStart = diagnostics.length;
|
|
4403
4518
|
addDiagnostic2(
|
|
4404
4519
|
diagnostics,
|
|
4405
4520
|
ssml,
|
|
4406
4521
|
token.start,
|
|
4407
4522
|
`<${token.name} ${attribute}> could not be validated by the custom URL validator: ${reason}`
|
|
4408
4523
|
);
|
|
4524
|
+
annotateTokenDiagnostics2(diagnostics, diagnosticStart, token, options, token.parentVoiceName);
|
|
4409
4525
|
}
|
|
4410
4526
|
})
|
|
4411
4527
|
);
|
|
@@ -4415,7 +4531,9 @@ var AZURE_VOICE_CATALOG_METADATA2 = {
|
|
|
4415
4531
|
apiVersion: "2025-10-01",
|
|
4416
4532
|
generatedAt: "2026-08-28T00:00:00.000Z",
|
|
4417
4533
|
regions: [],
|
|
4418
|
-
voiceCount: AZURE_VOICE_DEFINITIONS2.length
|
|
4534
|
+
voiceCount: AZURE_VOICE_DEFINITIONS2.length,
|
|
4535
|
+
expiresAt: "2026-09-04T00:00:00.000Z",
|
|
4536
|
+
regionDiffs: {}
|
|
4419
4537
|
};
|
|
4420
4538
|
|
|
4421
4539
|
// packages/azure-tts-client/src/outputFormats.ts
|
|
@@ -4532,6 +4650,115 @@ function parseWav(buffer) {
|
|
|
4532
4650
|
}
|
|
4533
4651
|
return { chunks, data, format };
|
|
4534
4652
|
}
|
|
4653
|
+
function formatNumber(format, pattern, fallback) {
|
|
4654
|
+
const match = pattern.exec(format);
|
|
4655
|
+
return match?.[1] ? Number(match[1]) : fallback;
|
|
4656
|
+
}
|
|
4657
|
+
function formatChannels(format, fallback) {
|
|
4658
|
+
if (/stereo|2ch|dual/i.test(format)) return 2;
|
|
4659
|
+
if (/mono|1ch/i.test(format)) return 1;
|
|
4660
|
+
return fallback;
|
|
4661
|
+
}
|
|
4662
|
+
function formatAudioSpecification(format) {
|
|
4663
|
+
const sampleRate = formatNumber(format, /(\d+)(?:khz|kHz|hz|Hz)/, 0);
|
|
4664
|
+
const channels = formatChannels(format, 0);
|
|
4665
|
+
const bitrateMatch = /(\d+)(?:kbitrate|kbps|kbits?)/i.exec(format);
|
|
4666
|
+
const bitrate = bitrateMatch?.[1] ? Number(bitrateMatch[1]) * 1e3 : void 0;
|
|
4667
|
+
const codec = /mp3|mpeg/i.test(format) ? "mp3" : /opus/i.test(format) ? "opus" : /silk/i.test(format) ? "silk" : /pcm|mulaw|alaw|siren/i.test(format) ? "pcm" : "unknown";
|
|
4668
|
+
const bitDepthMatch = /(\d+)bit/i.exec(format);
|
|
4669
|
+
const container = /(?:wav|wave|riff)/i.test(format) ? "riff-wave" : /mp3|mpeg/i.test(format) ? "mp3-raw" : /ogg/i.test(format) ? "ogg" : /webm/i.test(format) ? "webm" : /raw/i.test(format) ? "raw" : void 0;
|
|
4670
|
+
return {
|
|
4671
|
+
format,
|
|
4672
|
+
mimeType: resolveMimeType(format),
|
|
4673
|
+
codec,
|
|
4674
|
+
sampleRate,
|
|
4675
|
+
channels,
|
|
4676
|
+
...bitrate ? { bitrate } : {},
|
|
4677
|
+
...bitDepthMatch?.[1] ? { bitDepth: Number(bitDepthMatch[1]) } : {},
|
|
4678
|
+
...container ? { container } : {},
|
|
4679
|
+
isVbr: /vbr/i.test(format),
|
|
4680
|
+
isCompressed: codec === "mp3" || codec === "opus" || codec === "silk"
|
|
4681
|
+
};
|
|
4682
|
+
}
|
|
4683
|
+
function parseMp3Specification(buffer, format) {
|
|
4684
|
+
const bytes = stripMp3Tags(buffer);
|
|
4685
|
+
const bitrates = [
|
|
4686
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
4687
|
+
[0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0],
|
|
4688
|
+
[0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0]
|
|
4689
|
+
];
|
|
4690
|
+
const sampleRates = [
|
|
4691
|
+
[44100, 48e3, 32e3],
|
|
4692
|
+
[22050, 24e3, 16e3],
|
|
4693
|
+
[11025, 12e3, 8e3]
|
|
4694
|
+
];
|
|
4695
|
+
for (let index = 0; index + 4 <= bytes.length; index += 1) {
|
|
4696
|
+
if (bytes[index] !== 255 || (bytes[index + 1] ?? 0) < 224) continue;
|
|
4697
|
+
const header = bytes[index + 1] ?? 0;
|
|
4698
|
+
const versionBits = header >> 3 & 3;
|
|
4699
|
+
const layer = header >> 1 & 3;
|
|
4700
|
+
const bitrateIndex = (bytes[index + 2] ?? 0) >> 4;
|
|
4701
|
+
const sampleIndex = (bytes[index + 2] ?? 0) >> 2 & 3;
|
|
4702
|
+
if (versionBits === 1 || layer !== 1 || bitrateIndex === 0 || bitrateIndex === 15 || sampleIndex === 3) continue;
|
|
4703
|
+
const versionIndex = versionBits === 3 ? 0 : versionBits === 2 ? 1 : 2;
|
|
4704
|
+
const bitrateTable = versionBits === 3 ? bitrates[1] : bitrates[2];
|
|
4705
|
+
const sampleRate = sampleRates[versionIndex]?.[sampleIndex] ?? 0;
|
|
4706
|
+
const bitrateKbps = bitrateTable?.[bitrateIndex] ?? 0;
|
|
4707
|
+
if (!sampleRate || !bitrateKbps) continue;
|
|
4708
|
+
return {
|
|
4709
|
+
format,
|
|
4710
|
+
mimeType: "audio/mpeg",
|
|
4711
|
+
codec: "mp3",
|
|
4712
|
+
sampleRate,
|
|
4713
|
+
channels: (bytes[index + 3] ?? 0) >> 6 === 3 ? 1 : 2,
|
|
4714
|
+
bitrate: bitrateKbps * 1e3,
|
|
4715
|
+
container: "mp3-raw",
|
|
4716
|
+
isVbr: false,
|
|
4717
|
+
isCompressed: true
|
|
4718
|
+
};
|
|
4719
|
+
}
|
|
4720
|
+
return void 0;
|
|
4721
|
+
}
|
|
4722
|
+
function inspectAudioSpecification(buffer, format) {
|
|
4723
|
+
if (isWavFormat(format) || ascii(new Uint8Array(buffer), 0, "RIFF")) {
|
|
4724
|
+
const parsed = parseWav(buffer);
|
|
4725
|
+
if (parsed.format.byteLength < 16) throw new Error("Invalid WAV fmt chunk.");
|
|
4726
|
+
const view = new DataView(parsed.format.buffer, parsed.format.byteOffset, parsed.format.byteLength);
|
|
4727
|
+
const sampleRate = view.getUint32(4, true);
|
|
4728
|
+
const channels = view.getUint16(2, true);
|
|
4729
|
+
const bitsPerSample = parsed.format.byteLength >= 16 ? view.getUint16(14, true) : 0;
|
|
4730
|
+
const formatCode = view.getUint16(0, true);
|
|
4731
|
+
return {
|
|
4732
|
+
format,
|
|
4733
|
+
mimeType: "audio/wav",
|
|
4734
|
+
codec: formatCode === 1 ? "pcm" : "unknown",
|
|
4735
|
+
sampleRate,
|
|
4736
|
+
channels,
|
|
4737
|
+
...sampleRate && channels && bitsPerSample ? { bitrate: sampleRate * channels * bitsPerSample } : {},
|
|
4738
|
+
bitDepth: bitsPerSample,
|
|
4739
|
+
container: "riff-wave",
|
|
4740
|
+
isVbr: false,
|
|
4741
|
+
isCompressed: formatCode !== 1
|
|
4742
|
+
};
|
|
4743
|
+
}
|
|
4744
|
+
if (isMp3Format(format)) return parseMp3Specification(buffer, format) ?? formatAudioSpecification(format);
|
|
4745
|
+
return formatAudioSpecification(format);
|
|
4746
|
+
}
|
|
4747
|
+
function validateAudioSpecifications(specs) {
|
|
4748
|
+
const first = specs[0];
|
|
4749
|
+
if (!first) return;
|
|
4750
|
+
const mismatch = specs.find(
|
|
4751
|
+
(spec) => spec.sampleRate !== first.sampleRate || spec.channels !== first.channels || first.bitrate !== void 0 && spec.bitrate !== void 0 && spec.bitrate !== first.bitrate || first.bitDepth !== void 0 && spec.bitDepth !== void 0 && spec.bitDepth !== first.bitDepth || first.container !== void 0 && spec.container !== void 0 && spec.container !== first.container || first.isVbr !== void 0 && spec.isVbr !== void 0 && spec.isVbr !== first.isVbr
|
|
4752
|
+
);
|
|
4753
|
+
if (mismatch)
|
|
4754
|
+
throw new AudioFormatMismatchError(
|
|
4755
|
+
`Audio chunks have incompatible specifications: ${first.sampleRate}Hz/${first.channels}ch versus ${mismatch.sampleRate}Hz/${mismatch.channels}ch.`,
|
|
4756
|
+
specs
|
|
4757
|
+
);
|
|
4758
|
+
}
|
|
4759
|
+
function isAudioFormatMismatch(error) {
|
|
4760
|
+
return error instanceof AudioFormatMismatchError || error !== null && typeof error === "object" && "kind" in error && error.kind === "audio-format-mismatch";
|
|
4761
|
+
}
|
|
4535
4762
|
function writeUint32(target, offset, value) {
|
|
4536
4763
|
new DataView(target.buffer).setUint32(offset, value, true);
|
|
4537
4764
|
}
|
|
@@ -4617,6 +4844,7 @@ function mergeAudioBuffers(buffers, options) {
|
|
|
4617
4844
|
const format = typeof options === "string" ? options : options?.format;
|
|
4618
4845
|
if (!format) throw new UnsupportedMergeFormatError("");
|
|
4619
4846
|
try {
|
|
4847
|
+
validateAudioSpecifications(buffers.map((buffer) => inspectAudioSpecification(buffer, format)));
|
|
4620
4848
|
if (isWavFormat(format)) return mergeWavBuffers(buffers);
|
|
4621
4849
|
if (isMp3Format(format)) {
|
|
4622
4850
|
const parts = buffers.map(stripMp3Tags);
|
|
@@ -4639,7 +4867,8 @@ function mergeAudioBuffers(buffers, options) {
|
|
|
4639
4867
|
}
|
|
4640
4868
|
throw new UnsupportedMergeFormatError(format);
|
|
4641
4869
|
} catch (error) {
|
|
4642
|
-
if (error instanceof UnsupportedMergeFormatError || error instanceof MergeError)
|
|
4870
|
+
if (error instanceof UnsupportedMergeFormatError || isAudioFormatMismatch(error) || error instanceof MergeError)
|
|
4871
|
+
throw error;
|
|
4643
4872
|
throw new MergeError(`Audio buffers could not be merged for format "${format}".`, error);
|
|
4644
4873
|
}
|
|
4645
4874
|
}
|
|
@@ -4717,17 +4946,24 @@ async function synthesizeSsml(ssml, config) {
|
|
|
4717
4946
|
return {
|
|
4718
4947
|
originalTextRange: { ...marker.originalTextRange },
|
|
4719
4948
|
sourceNodePath: [...marker.sourceNodePath],
|
|
4720
|
-
textRange: { ...marker.originalTextRange }
|
|
4949
|
+
textRange: { ...marker.originalTextRange },
|
|
4950
|
+
mappingStatus: "exact"
|
|
4721
4951
|
};
|
|
4722
4952
|
}
|
|
4723
|
-
if (sourceSegments.length === 0 && !config.sourceTextRange && !config.sourceNodePath)
|
|
4953
|
+
if (sourceSegments.length === 0 && !config.sourceTextRange && !config.sourceNodePath) {
|
|
4954
|
+
return { mappingStatus: "unmapped" };
|
|
4955
|
+
}
|
|
4724
4956
|
const value = text ?? "";
|
|
4725
4957
|
let localStart = Number.isFinite(offsetHint) && (offsetHint ?? 0) >= 0 ? offsetHint : -1;
|
|
4726
|
-
|
|
4958
|
+
let mappingStatus = "exact";
|
|
4959
|
+
if (value && localStart >= 0 && sourceText.slice(localStart, localStart + value.length) !== value) {
|
|
4727
4960
|
localStart = -1;
|
|
4961
|
+
mappingStatus = "fallback";
|
|
4962
|
+
}
|
|
4728
4963
|
if (localStart < 0 || localStart > sourceText.length) {
|
|
4729
4964
|
localStart = value ? sourceText.indexOf(value, sourceEventCursor) : sourceEventCursor;
|
|
4730
4965
|
if (localStart < 0) localStart = value ? sourceText.indexOf(value) : sourceEventCursor;
|
|
4966
|
+
mappingStatus = "fallback";
|
|
4731
4967
|
}
|
|
4732
4968
|
localStart = Math.max(0, localStart);
|
|
4733
4969
|
const localEnd = Math.min(sourceText.length, localStart + value.length);
|
|
@@ -4738,7 +4974,8 @@ async function synthesizeSsml(ssml, config) {
|
|
|
4738
4974
|
return {
|
|
4739
4975
|
originalTextRange: { ...fallbackRange },
|
|
4740
4976
|
textRange: { ...fallbackRange },
|
|
4741
|
-
...segment ? { sourceNodePath: [...segment.sourceNodePath] } : config.sourceNodePath ? { sourceNodePath: [...config.sourceNodePath] } : {}
|
|
4977
|
+
...segment ? { sourceNodePath: [...segment.sourceNodePath] } : config.sourceNodePath ? { sourceNodePath: [...config.sourceNodePath] } : {},
|
|
4978
|
+
mappingStatus: segment || config.sourceTextRange || config.sourceNodePath ? mappingStatus : "unmapped"
|
|
4742
4979
|
};
|
|
4743
4980
|
};
|
|
4744
4981
|
synthesizer.wordBoundary = (_sender, event) => {
|
|
@@ -4787,20 +5024,32 @@ async function synthesizeSsml(ssml, config) {
|
|
|
4787
5024
|
);
|
|
4788
5025
|
const durationMs = result.audioDuration ? ticksToMilliseconds(result.audioDuration) : eventDurationMs;
|
|
4789
5026
|
const requestId = result.resultId;
|
|
4790
|
-
const addSourceMetadata = (event) =>
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
5027
|
+
const addSourceMetadata = (event) => {
|
|
5028
|
+
const mapped = {
|
|
5029
|
+
...event,
|
|
5030
|
+
...config.sourceTextRange && !("textRange" in event) ? { textRange: { ...config.sourceTextRange } } : {},
|
|
5031
|
+
...config.sourceTextRange && !("originalTextRange" in event) ? { originalTextRange: { ...config.sourceTextRange } } : {},
|
|
5032
|
+
...config.chunkIndex !== void 0 ? { chunkIndex: config.chunkIndex } : {},
|
|
5033
|
+
...config.sourceNodePath ? { sourceNodePath: [...config.sourceNodePath] } : {},
|
|
5034
|
+
...requestId ? { requestId } : {}
|
|
5035
|
+
};
|
|
5036
|
+
if (event.mappingStatus === "unmapped") {
|
|
5037
|
+
Object.defineProperty(mapped, "mappingStatus", { value: "unmapped", enumerable: false });
|
|
5038
|
+
Object.defineProperty(mapped, "toJSON", {
|
|
5039
|
+
value: () => ({ ...mapped, mappingStatus: "unmapped" }),
|
|
5040
|
+
enumerable: false
|
|
5041
|
+
});
|
|
5042
|
+
}
|
|
5043
|
+
return mapped;
|
|
5044
|
+
};
|
|
4798
5045
|
const sourceBoundaries = boundaries.map((boundary) => addSourceMetadata(boundary));
|
|
4799
5046
|
const sourceVisemes = visemes.map((viseme) => addSourceMetadata(viseme));
|
|
4800
5047
|
const sourceBookmarks = bookmarks.map((bookmark) => addSourceMetadata(bookmark));
|
|
4801
5048
|
resolve({
|
|
4802
5049
|
audioData: result.audioData,
|
|
4803
5050
|
durationMs,
|
|
5051
|
+
audioSpec: inspectAudioSpecification(result.audioData, config.outputFormat ?? DEFAULT_OUTPUT_FORMAT),
|
|
5052
|
+
mimeType: resolveMimeType(config.outputFormat ?? DEFAULT_OUTPUT_FORMAT),
|
|
4804
5053
|
...config.sourceTextRange ? { textRange: { ...config.sourceTextRange } } : {},
|
|
4805
5054
|
...requestId ? { requestId } : {},
|
|
4806
5055
|
...sourceBoundaries.length > 0 ? { boundaries: sourceBoundaries, wordBoundary: sourceBoundaries, wordBoundaries: sourceBoundaries } : {},
|
|
@@ -4813,10 +5062,11 @@ async function synthesizeSsml(ssml, config) {
|
|
|
4813
5062
|
abortHandler = () => rejectWithError(new SynthesisCancelledError());
|
|
4814
5063
|
config.signal.addEventListener("abort", abortHandler, { once: true });
|
|
4815
5064
|
}
|
|
4816
|
-
|
|
5065
|
+
const timeoutMs = config.timeouts?.perChunkMs ?? config.timeoutMs;
|
|
5066
|
+
if (timeoutMs !== void 0 && timeoutMs > 0) {
|
|
4817
5067
|
timeout = setTimeout(
|
|
4818
|
-
() => rejectWithError(new SynthesisTimeoutError(`Speech synthesis timed out after ${
|
|
4819
|
-
|
|
5068
|
+
() => rejectWithError(new SynthesisTimeoutError(`Speech synthesis timed out after ${timeoutMs} ms.`)),
|
|
5069
|
+
timeoutMs
|
|
4820
5070
|
);
|
|
4821
5071
|
}
|
|
4822
5072
|
synthesizer.speakSsmlAsync(ssml, cb, rejectWithError);
|
|
@@ -4825,9 +5075,109 @@ async function synthesizeSsml(ssml, config) {
|
|
|
4825
5075
|
}
|
|
4826
5076
|
});
|
|
4827
5077
|
}
|
|
5078
|
+
function isRetryableSynthesisError(error) {
|
|
5079
|
+
if (error instanceof SynthesisCancelledError || error instanceof SynthesisTimeoutError) return false;
|
|
5080
|
+
if (error instanceof AzureTtsError && error.status !== 0)
|
|
5081
|
+
return error.status === 429 || error.status >= 500 && error.status < 600;
|
|
5082
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5083
|
+
if (/\b4\d{2}\b/.test(message)) return false;
|
|
5084
|
+
const status = error && typeof error === "object" && "status" in error ? error.status : void 0;
|
|
5085
|
+
if (typeof status === "number") return status === 429 || status >= 500 && status < 600;
|
|
5086
|
+
return /network|connection|connect|socket|fetch failed|econn|etimedout|temporar|transient|unavailable/i.test(message);
|
|
5087
|
+
}
|
|
5088
|
+
function retryDelay(options, retryAttempt, error) {
|
|
5089
|
+
const retryAfterMs = getRetryAfterDelayMs(error);
|
|
5090
|
+
if (retryAfterMs !== void 0) return retryAfterMs;
|
|
5091
|
+
const base = Math.min(options.maxDelayMs, options.initialDelayMs * 2 ** Math.max(0, retryAttempt - 1));
|
|
5092
|
+
return Math.floor(Math.random() * (base + 1));
|
|
5093
|
+
}
|
|
5094
|
+
function resolveConcurrency(value, total) {
|
|
5095
|
+
if (value === void 0) return 1;
|
|
5096
|
+
if (value === Infinity) return Math.max(1, total);
|
|
5097
|
+
return Number.isFinite(value) ? Math.max(1, Math.floor(value)) : 1;
|
|
5098
|
+
}
|
|
5099
|
+
async function waitForRetry(delayMs, signal) {
|
|
5100
|
+
if (signal?.aborted) throw new SynthesisCancelledError();
|
|
5101
|
+
if (delayMs <= 0) return;
|
|
5102
|
+
await new Promise((resolve, reject) => {
|
|
5103
|
+
let timer;
|
|
5104
|
+
const abort = () => {
|
|
5105
|
+
clearTimeout(timer);
|
|
5106
|
+
signal?.removeEventListener("abort", abort);
|
|
5107
|
+
reject(new SynthesisCancelledError());
|
|
5108
|
+
};
|
|
5109
|
+
timer = setTimeout(() => {
|
|
5110
|
+
signal?.removeEventListener("abort", abort);
|
|
5111
|
+
resolve();
|
|
5112
|
+
}, delayMs);
|
|
5113
|
+
if (signal) {
|
|
5114
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
5115
|
+
}
|
|
5116
|
+
});
|
|
5117
|
+
}
|
|
5118
|
+
async function synthesizeWithRetry(ssml, config, retryOptions, onRetry) {
|
|
5119
|
+
const options = retryOptions ? {
|
|
5120
|
+
maxRetries: Math.max(0, Math.floor(retryOptions.maxRetries)),
|
|
5121
|
+
initialDelayMs: Math.max(0, retryOptions.initialDelayMs),
|
|
5122
|
+
maxDelayMs: Math.max(0, retryOptions.maxDelayMs),
|
|
5123
|
+
shouldRetry: retryOptions.shouldRetry
|
|
5124
|
+
} : void 0;
|
|
5125
|
+
let attempt = 0;
|
|
5126
|
+
while (true) {
|
|
5127
|
+
if (config.signal?.aborted) throw new SynthesisCancelledError();
|
|
5128
|
+
try {
|
|
5129
|
+
return await synthesizeSsml(ssml, config);
|
|
5130
|
+
} catch (error) {
|
|
5131
|
+
if (!options || attempt >= options.maxRetries || !(options.shouldRetry?.(error, attempt + 1) ?? isRetryableSynthesisError(error)))
|
|
5132
|
+
throw error;
|
|
5133
|
+
attempt += 1;
|
|
5134
|
+
const delayMs = retryDelay(options, attempt, error);
|
|
5135
|
+
onRetry(attempt, delayMs);
|
|
5136
|
+
await waitForRetry(delayMs, config.signal);
|
|
5137
|
+
}
|
|
5138
|
+
}
|
|
5139
|
+
}
|
|
5140
|
+
function createAbortScope(parent, timeoutMs) {
|
|
5141
|
+
const controller = new AbortController();
|
|
5142
|
+
let didTimeout = false;
|
|
5143
|
+
const onAbort = () => controller.abort();
|
|
5144
|
+
if (parent?.aborted) controller.abort();
|
|
5145
|
+
parent?.addEventListener("abort", onAbort, { once: true });
|
|
5146
|
+
const timer = timeoutMs !== void 0 && timeoutMs > 0 ? setTimeout(() => {
|
|
5147
|
+
didTimeout = true;
|
|
5148
|
+
controller.abort();
|
|
5149
|
+
}, timeoutMs) : void 0;
|
|
5150
|
+
return {
|
|
5151
|
+
signal: controller.signal,
|
|
5152
|
+
timedOut: () => didTimeout,
|
|
5153
|
+
dispose: () => {
|
|
5154
|
+
if (timer) clearTimeout(timer);
|
|
5155
|
+
parent?.removeEventListener("abort", onAbort);
|
|
5156
|
+
},
|
|
5157
|
+
abort: () => controller.abort()
|
|
5158
|
+
};
|
|
5159
|
+
}
|
|
5160
|
+
async function synthesizeChunkWithTimeout(ssml, config, retryOptions, timeoutMs, onRetry) {
|
|
5161
|
+
const scope = createAbortScope(config.signal, timeoutMs);
|
|
5162
|
+
try {
|
|
5163
|
+
return await synthesizeWithRetry(ssml, { ...config, signal: scope.signal }, retryOptions, onRetry);
|
|
5164
|
+
} catch (error) {
|
|
5165
|
+
if (scope.timedOut()) throw new SynthesisTimeoutError(`Speech synthesis timed out after ${timeoutMs} ms.`);
|
|
5166
|
+
throw error;
|
|
5167
|
+
} finally {
|
|
5168
|
+
scope.dispose();
|
|
5169
|
+
}
|
|
5170
|
+
}
|
|
4828
5171
|
async function synthesizeSsmlChunks(chunks, config) {
|
|
4829
|
-
const results =
|
|
5172
|
+
const results = new Array(chunks.length);
|
|
4830
5173
|
const totalChunks = chunks.length;
|
|
5174
|
+
const cachedChunks = new Map((config.resumeChunks ?? []).map((chunk) => [chunk.chunkIndex, chunk]));
|
|
5175
|
+
for (const [index, cached] of cachedChunks) {
|
|
5176
|
+
if (index >= 0 && index < totalChunks) results[index] = cached;
|
|
5177
|
+
}
|
|
5178
|
+
const requestedIndices = config.resumeChunkIndices ? new Set(config.resumeChunkIndices.filter((index) => index >= 0 && index < totalChunks)) : void 0;
|
|
5179
|
+
const shouldSynthesize = (index) => !cachedChunks.has(index) && (requestedIndices === void 0 || requestedIndices.has(index));
|
|
5180
|
+
const scope = createAbortScope(config.signal, config.timeouts?.totalJobMs);
|
|
4831
5181
|
const report = (event) => config.onProgress?.(event);
|
|
4832
5182
|
for (const [index, chunk] of chunks.entries()) {
|
|
4833
5183
|
const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
|
|
@@ -4841,57 +5191,112 @@ async function synthesizeSsmlChunks(chunks, config) {
|
|
|
4841
5191
|
durationMs: 0
|
|
4842
5192
|
});
|
|
4843
5193
|
}
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
const result = await synthesizeSsml(input.ssml, {
|
|
4858
|
-
...config,
|
|
4859
|
-
...input.originalTextRange ? { sourceTextRange: input.originalTextRange } : {},
|
|
4860
|
-
...input.sourceNodePath ?? config.sourceNodePath ? { sourceNodePath: [...input.sourceNodePath ?? config.sourceNodePath ?? []] } : {},
|
|
4861
|
-
...input.sourceTextSegments ? { sourceTextSegments: input.sourceTextSegments } : {},
|
|
4862
|
-
...input.sourceMarkers ? { sourceMarkers: input.sourceMarkers } : {},
|
|
4863
|
-
chunkIndex: index,
|
|
4864
|
-
onProgress: void 0
|
|
4865
|
-
});
|
|
4866
|
-
results.push(result);
|
|
4867
|
-
report({
|
|
4868
|
-
currentChunk: index + 1,
|
|
4869
|
-
totalChunks,
|
|
4870
|
-
percent: totalChunks === 0 ? 100 : Math.round((index + 1) / totalChunks * 100),
|
|
4871
|
-
chunkIndex: index,
|
|
4872
|
-
originalTextRange: input.originalTextRange,
|
|
4873
|
-
status: "success",
|
|
4874
|
-
durationMs: Date.now() - startedAt
|
|
4875
|
-
});
|
|
4876
|
-
} catch (error) {
|
|
5194
|
+
let completed = [...results].filter((result) => result !== void 0).length;
|
|
5195
|
+
let nextIndex = 0;
|
|
5196
|
+
const concurrency = resolveConcurrency(config.concurrency, chunks.length);
|
|
5197
|
+
let firstError;
|
|
5198
|
+
const failedIndices = /* @__PURE__ */ new Set();
|
|
5199
|
+
const worker = async () => {
|
|
5200
|
+
while (true) {
|
|
5201
|
+
const index = nextIndex++;
|
|
5202
|
+
if (index >= chunks.length) return;
|
|
5203
|
+
if (!shouldSynthesize(index)) continue;
|
|
5204
|
+
if (firstError && config.cancelOnFailure !== false) return;
|
|
5205
|
+
const chunk = chunks[index];
|
|
5206
|
+
const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
|
|
4877
5207
|
report({
|
|
4878
|
-
currentChunk:
|
|
5208
|
+
currentChunk: completed,
|
|
4879
5209
|
totalChunks,
|
|
4880
|
-
percent: totalChunks === 0 ? 100 : Math.round(
|
|
5210
|
+
percent: totalChunks === 0 ? 100 : Math.round(completed / totalChunks * 100),
|
|
4881
5211
|
chunkIndex: index,
|
|
4882
5212
|
originalTextRange: input.originalTextRange,
|
|
4883
|
-
status: "
|
|
4884
|
-
durationMs:
|
|
4885
|
-
error
|
|
5213
|
+
status: "synthesizing",
|
|
5214
|
+
durationMs: 0
|
|
4886
5215
|
});
|
|
4887
|
-
|
|
5216
|
+
const startedAt = Date.now();
|
|
5217
|
+
try {
|
|
5218
|
+
const result = await synthesizeChunkWithTimeout(
|
|
5219
|
+
input.ssml,
|
|
5220
|
+
{
|
|
5221
|
+
...config,
|
|
5222
|
+
signal: scope.signal,
|
|
5223
|
+
...input.originalTextRange ? { sourceTextRange: input.originalTextRange } : {},
|
|
5224
|
+
...input.sourceNodePath ?? config.sourceNodePath ? { sourceNodePath: [...input.sourceNodePath ?? config.sourceNodePath ?? []] } : {},
|
|
5225
|
+
...input.sourceTextSegments ? { sourceTextSegments: input.sourceTextSegments } : {},
|
|
5226
|
+
...input.sourceMarkers ? { sourceMarkers: input.sourceMarkers } : {},
|
|
5227
|
+
chunkIndex: index,
|
|
5228
|
+
onProgress: void 0
|
|
5229
|
+
},
|
|
5230
|
+
config.retryOptions,
|
|
5231
|
+
config.timeouts?.chunkWithRetriesMs ?? config.timeouts?.perChunkMs ?? config.timeoutMs,
|
|
5232
|
+
(retryAttempt, nextRetryDelayMs) => report({
|
|
5233
|
+
currentChunk: completed,
|
|
5234
|
+
totalChunks,
|
|
5235
|
+
percent: totalChunks === 0 ? 100 : Math.round(completed / totalChunks * 100),
|
|
5236
|
+
chunkIndex: index,
|
|
5237
|
+
originalTextRange: input.originalTextRange,
|
|
5238
|
+
status: "synthesizing",
|
|
5239
|
+
durationMs: Date.now() - startedAt,
|
|
5240
|
+
retryAttempt,
|
|
5241
|
+
nextRetryDelayMs,
|
|
5242
|
+
isRetrying: true
|
|
5243
|
+
})
|
|
5244
|
+
);
|
|
5245
|
+
results[index] = result;
|
|
5246
|
+
completed += 1;
|
|
5247
|
+
report({
|
|
5248
|
+
currentChunk: completed,
|
|
5249
|
+
totalChunks,
|
|
5250
|
+
percent: totalChunks === 0 ? 100 : Math.round(completed / totalChunks * 100),
|
|
5251
|
+
chunkIndex: index,
|
|
5252
|
+
originalTextRange: input.originalTextRange,
|
|
5253
|
+
status: "success",
|
|
5254
|
+
durationMs: Date.now() - startedAt
|
|
5255
|
+
});
|
|
5256
|
+
} catch (error) {
|
|
5257
|
+
failedIndices.add(index);
|
|
5258
|
+
report({
|
|
5259
|
+
currentChunk: completed,
|
|
5260
|
+
totalChunks,
|
|
5261
|
+
percent: totalChunks === 0 ? 100 : Math.round(completed / totalChunks * 100),
|
|
5262
|
+
chunkIndex: index,
|
|
5263
|
+
originalTextRange: input.originalTextRange,
|
|
5264
|
+
status: "failed",
|
|
5265
|
+
durationMs: Date.now() - startedAt,
|
|
5266
|
+
error
|
|
5267
|
+
});
|
|
5268
|
+
firstError ?? (firstError = scope.timedOut() ? new SynthesisTimeoutError(`Speech synthesis timed out after ${config.timeouts?.totalJobMs} ms.`) : error);
|
|
5269
|
+
if (config.cancelOnFailure !== false) scope.abort();
|
|
5270
|
+
return;
|
|
5271
|
+
}
|
|
4888
5272
|
}
|
|
5273
|
+
};
|
|
5274
|
+
try {
|
|
5275
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, chunks.length) }, () => worker()));
|
|
5276
|
+
if (firstError) throw firstError;
|
|
5277
|
+
const orderedResults = results.filter((result) => result !== void 0);
|
|
5278
|
+
return await mergeSynthesisResults(orderedResults, {
|
|
5279
|
+
format: config.outputFormat ?? DEFAULT_OUTPUT_FORMAT,
|
|
5280
|
+
signal: scope.signal,
|
|
5281
|
+
customMerger: config.customMerger,
|
|
5282
|
+
outputMimeType: config.outputMimeType,
|
|
5283
|
+
postMergeValidator: config.postMergeValidator
|
|
5284
|
+
});
|
|
5285
|
+
} catch (error) {
|
|
5286
|
+
const partial = {
|
|
5287
|
+
synthesizedChunks: results.flatMap((result, chunkIndex) => result ? [{ ...result, chunkIndex }] : []),
|
|
5288
|
+
completedChunks: results.flatMap((result, chunkIndex) => result ? [{ ...result, chunkIndex }] : []),
|
|
5289
|
+
pendingChunkIndices: chunks.flatMap((_chunk, chunkIndex) => results[chunkIndex] ? [] : [chunkIndex]),
|
|
5290
|
+
failedChunkIndices: [...failedIndices],
|
|
5291
|
+
totalChunks
|
|
5292
|
+
};
|
|
5293
|
+
if (error && typeof error === "object") error.partialResult = partial;
|
|
5294
|
+
throw error;
|
|
5295
|
+
} finally {
|
|
5296
|
+
scope.dispose();
|
|
4889
5297
|
}
|
|
4890
|
-
return mergeSynthesisResults(results, {
|
|
4891
|
-
format: config.outputFormat ?? "audio-16khz-128kbitrate-mono-mp3"
|
|
4892
|
-
});
|
|
4893
5298
|
}
|
|
4894
|
-
function createMergedResult(results, audioData, format) {
|
|
5299
|
+
function createMergedResult(results, audioData, format, audioSpec, outputMimeType) {
|
|
4895
5300
|
const boundaries = [];
|
|
4896
5301
|
const visemes = [];
|
|
4897
5302
|
const bookmarks = [];
|
|
@@ -4910,7 +5315,8 @@ function createMergedResult(results, audioData, format) {
|
|
|
4910
5315
|
...boundary.sourceNodePath ? { sourceNodePath: [...boundary.sourceNodePath] } : {},
|
|
4911
5316
|
...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
|
|
4912
5317
|
...textRange ? { textRange: { ...textRange } } : {},
|
|
4913
|
-
...requestId ? { requestId } : {}
|
|
5318
|
+
...requestId ? { requestId } : {},
|
|
5319
|
+
mappingStatus: boundary.mappingStatus ?? "unmapped"
|
|
4914
5320
|
});
|
|
4915
5321
|
}
|
|
4916
5322
|
for (const viseme of result.visemes ?? []) {
|
|
@@ -4925,7 +5331,8 @@ function createMergedResult(results, audioData, format) {
|
|
|
4925
5331
|
...viseme.sourceNodePath ? { sourceNodePath: [...viseme.sourceNodePath] } : {},
|
|
4926
5332
|
...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
|
|
4927
5333
|
...textRange ? { textRange: { ...textRange } } : {},
|
|
4928
|
-
...requestId ? { requestId } : {}
|
|
5334
|
+
...requestId ? { requestId } : {},
|
|
5335
|
+
mappingStatus: viseme.mappingStatus ?? "unmapped"
|
|
4929
5336
|
});
|
|
4930
5337
|
}
|
|
4931
5338
|
for (const bookmark of result.bookmarks ?? []) {
|
|
@@ -4940,7 +5347,8 @@ function createMergedResult(results, audioData, format) {
|
|
|
4940
5347
|
...bookmark.sourceNodePath ? { sourceNodePath: [...bookmark.sourceNodePath] } : {},
|
|
4941
5348
|
...originalTextRange ? { originalTextRange: { ...originalTextRange } } : {},
|
|
4942
5349
|
...textRange ? { textRange: { ...textRange } } : {},
|
|
4943
|
-
...requestId ? { requestId } : {}
|
|
5350
|
+
...requestId ? { requestId } : {},
|
|
5351
|
+
mappingStatus: bookmark.mappingStatus ?? "unmapped"
|
|
4944
5352
|
});
|
|
4945
5353
|
}
|
|
4946
5354
|
durationOffset += Math.max(0, result.durationMs);
|
|
@@ -4949,6 +5357,8 @@ function createMergedResult(results, audioData, format) {
|
|
|
4949
5357
|
audioData,
|
|
4950
5358
|
durationMs: durationOffset,
|
|
4951
5359
|
mimeType: resolveMimeType(format),
|
|
5360
|
+
audioSpec: audioSpec ?? formatAudioSpecification(format),
|
|
5361
|
+
...outputMimeType ? { mimeType: outputMimeType } : {},
|
|
4952
5362
|
...boundaries.length > 0 ? { boundaries, wordBoundary: boundaries, wordBoundaries: boundaries } : {},
|
|
4953
5363
|
...visemes.length > 0 ? { visemes } : {},
|
|
4954
5364
|
...bookmarks.length > 0 ? { bookmarks } : {},
|
|
@@ -4961,19 +5371,73 @@ function mergeSynthesisResults(results, options) {
|
|
|
4961
5371
|
const format = resolvedOptions?.format;
|
|
4962
5372
|
if (!format) throw new UnsupportedMergeFormatError("");
|
|
4963
5373
|
const buffers = results.map((result) => result.audioData);
|
|
5374
|
+
const inputSpecs = results.map((result) => result.audioSpec ?? inspectAudioSpecification(result.audioData, format));
|
|
5375
|
+
validateAudioSpecifications(inputSpecs);
|
|
5376
|
+
const signal = resolvedOptions.signal ?? new AbortController().signal;
|
|
5377
|
+
if (signal.aborted) throw new SynthesisCancelledError();
|
|
4964
5378
|
if (resolvedOptions.customMerger) {
|
|
4965
|
-
return Promise.resolve().then(
|
|
5379
|
+
return Promise.resolve().then(
|
|
5380
|
+
() => resolvedOptions.customMerger?.(buffers, {
|
|
5381
|
+
format,
|
|
5382
|
+
outputMimeType: resolvedOptions.outputMimeType ?? resolveMimeType(format),
|
|
5383
|
+
inputSpecs,
|
|
5384
|
+
signal
|
|
5385
|
+
})
|
|
5386
|
+
).then((merged) => {
|
|
4966
5387
|
if (!merged) throw new MergeError("The custom audio merger returned no audio buffer.");
|
|
4967
|
-
|
|
5388
|
+
if (!(merged instanceof ArrayBuffer) || buffers.some((buffer) => buffer.byteLength > 0) && merged.byteLength === 0)
|
|
5389
|
+
throw new MergeError("The custom audio merger returned an invalid audio buffer.");
|
|
5390
|
+
if (signal.aborted) throw new SynthesisCancelledError();
|
|
5391
|
+
const result = createMergedResult(
|
|
5392
|
+
results,
|
|
5393
|
+
merged,
|
|
5394
|
+
format,
|
|
5395
|
+
inspectAudioSpecification(merged, format),
|
|
5396
|
+
resolvedOptions.outputMimeType
|
|
5397
|
+
);
|
|
5398
|
+
return Promise.resolve(
|
|
5399
|
+
resolvedOptions.postMergeValidator?.(result, {
|
|
5400
|
+
format,
|
|
5401
|
+
outputMimeType: resolvedOptions.outputMimeType ?? resolveMimeType(format),
|
|
5402
|
+
inputSpecs,
|
|
5403
|
+
signal
|
|
5404
|
+
})
|
|
5405
|
+
).then((valid) => {
|
|
5406
|
+
if (valid === false) throw new MergeError("The post-merge validator rejected the merged audio.");
|
|
5407
|
+
return result;
|
|
5408
|
+
});
|
|
4968
5409
|
}).catch((error) => {
|
|
4969
|
-
if (error instanceof UnsupportedMergeFormatError || error instanceof MergeError)
|
|
5410
|
+
if (error instanceof UnsupportedMergeFormatError || isAudioFormatMismatch(error) || error instanceof MergeError)
|
|
5411
|
+
throw error;
|
|
4970
5412
|
throw new MergeError(`Custom audio merger failed for format "${format}".`, error);
|
|
4971
5413
|
});
|
|
4972
5414
|
}
|
|
4973
5415
|
try {
|
|
4974
|
-
|
|
5416
|
+
const result = createMergedResult(
|
|
5417
|
+
results,
|
|
5418
|
+
mergeAudioBuffers(buffers, { format }),
|
|
5419
|
+
format,
|
|
5420
|
+
inputSpecs[0],
|
|
5421
|
+
resolvedOptions.outputMimeType
|
|
5422
|
+
);
|
|
5423
|
+
if (resolvedOptions.postMergeValidator) {
|
|
5424
|
+
const validation = resolvedOptions.postMergeValidator(result, {
|
|
5425
|
+
format,
|
|
5426
|
+
outputMimeType: resolvedOptions.outputMimeType ?? resolveMimeType(format),
|
|
5427
|
+
inputSpecs,
|
|
5428
|
+
signal
|
|
5429
|
+
});
|
|
5430
|
+
if (validation instanceof Promise)
|
|
5431
|
+
return validation.then((valid) => {
|
|
5432
|
+
if (valid === false) throw new MergeError("The post-merge validator rejected the merged audio.");
|
|
5433
|
+
return result;
|
|
5434
|
+
});
|
|
5435
|
+
if (validation === false) throw new MergeError("The post-merge validator rejected the merged audio.");
|
|
5436
|
+
}
|
|
5437
|
+
return result;
|
|
4975
5438
|
} catch (error) {
|
|
4976
|
-
if (error instanceof UnsupportedMergeFormatError || error instanceof MergeError)
|
|
5439
|
+
if (error instanceof UnsupportedMergeFormatError || isAudioFormatMismatch(error) || error instanceof MergeError)
|
|
5440
|
+
throw error;
|
|
4977
5441
|
throw new MergeError(`Audio buffers could not be merged for format "${format}".`, error);
|
|
4978
5442
|
}
|
|
4979
5443
|
}
|
|
@@ -4991,11 +5455,125 @@ var ChunkValidationError = class extends Error {
|
|
|
4991
5455
|
this.diagnostics = diagnostics;
|
|
4992
5456
|
}
|
|
4993
5457
|
};
|
|
4994
|
-
|
|
4995
|
-
|
|
5458
|
+
var BatchChunkValidationError = class extends ChunkValidationError {
|
|
5459
|
+
constructor(chunkDiagnostics) {
|
|
5460
|
+
const first = chunkDiagnostics[0];
|
|
5461
|
+
super(first?.chunkIndex ?? -1, first?.diagnostics ?? []);
|
|
5462
|
+
this.name = "BatchChunkValidationError";
|
|
5463
|
+
this.message = `SSML validation failed for ${chunkDiagnostics.length} chunk(s); the Azure Speech API was not called.`;
|
|
5464
|
+
this.chunkDiagnostics = chunkDiagnostics;
|
|
5465
|
+
this.totalErrorCount = chunkDiagnostics.reduce((total, chunk) => total + chunk.diagnostics.length, 0);
|
|
5466
|
+
this.errorCount = this.totalErrorCount;
|
|
5467
|
+
this.totalErrors = this.totalErrorCount;
|
|
5468
|
+
}
|
|
5469
|
+
};
|
|
5470
|
+
function failure(error, partialResult) {
|
|
5471
|
+
return {
|
|
5472
|
+
ok: false,
|
|
5473
|
+
success: false,
|
|
5474
|
+
status: error.kind,
|
|
5475
|
+
error,
|
|
5476
|
+
...partialResult ? { partialResult } : {}
|
|
5477
|
+
};
|
|
5478
|
+
}
|
|
5479
|
+
function partialResultFrom(error) {
|
|
5480
|
+
if (!error || typeof error !== "object") return void 0;
|
|
5481
|
+
const partial = error.partialResult;
|
|
5482
|
+
if (!partial || typeof partial !== "object") return void 0;
|
|
5483
|
+
return partial;
|
|
5484
|
+
}
|
|
5485
|
+
function createSafeAbortScope(parent, timeoutMs) {
|
|
5486
|
+
const controller = new AbortController();
|
|
5487
|
+
let didTimeout = false;
|
|
5488
|
+
const onAbort = () => controller.abort();
|
|
5489
|
+
if (parent?.aborted) controller.abort();
|
|
5490
|
+
parent?.addEventListener("abort", onAbort, { once: true });
|
|
5491
|
+
const timer = timeoutMs !== void 0 && timeoutMs > 0 ? setTimeout(() => {
|
|
5492
|
+
didTimeout = true;
|
|
5493
|
+
controller.abort();
|
|
5494
|
+
}, timeoutMs) : void 0;
|
|
5495
|
+
return {
|
|
5496
|
+
signal: controller.signal,
|
|
5497
|
+
timedOut: () => didTimeout,
|
|
5498
|
+
dispose: () => {
|
|
5499
|
+
if (timer) clearTimeout(timer);
|
|
5500
|
+
parent?.removeEventListener("abort", onAbort);
|
|
5501
|
+
},
|
|
5502
|
+
abort: () => controller.abort()
|
|
5503
|
+
};
|
|
5504
|
+
}
|
|
5505
|
+
function isRetryable(error) {
|
|
5506
|
+
if (error instanceof Error && /cancel|abort|tim(?:e|ed) ?out/i.test(error.message)) return false;
|
|
5507
|
+
const status = error && typeof error === "object" && "status" in error ? error.status : void 0;
|
|
5508
|
+
if (typeof status === "number" && status !== 0) return status === 429 || status >= 500 && status < 600;
|
|
5509
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5510
|
+
if (/\b4\d{2}\b/.test(message)) return false;
|
|
5511
|
+
return /network|connection|connect|socket|fetch failed|econn|etimedout|temporar|transient|unavailable/i.test(message);
|
|
5512
|
+
}
|
|
5513
|
+
function delayForRetry(options, attempt) {
|
|
5514
|
+
const maxDelay = Math.max(0, options.maxDelayMs);
|
|
5515
|
+
const base = Math.min(maxDelay, Math.max(0, options.initialDelayMs) * 2 ** Math.max(0, attempt - 1));
|
|
5516
|
+
return Math.floor(Math.random() * (base + 1));
|
|
5517
|
+
}
|
|
5518
|
+
function retryDelayForError(options, attempt, error) {
|
|
5519
|
+
return getRetryAfterDelayMs(error) ?? delayForRetry(options, attempt);
|
|
5520
|
+
}
|
|
5521
|
+
function resolveConcurrency2(value, total) {
|
|
5522
|
+
if (value === void 0) return 1;
|
|
5523
|
+
if (value === Infinity) return Math.max(1, total);
|
|
5524
|
+
return Number.isFinite(value) ? Math.max(1, Math.floor(value)) : 1;
|
|
5525
|
+
}
|
|
5526
|
+
async function retryableSynthesis(synthesize, options, signal, onRetry) {
|
|
5527
|
+
const retry = options ? {
|
|
5528
|
+
maxRetries: Math.max(0, Math.floor(options.maxRetries)),
|
|
5529
|
+
initialDelayMs: options.initialDelayMs,
|
|
5530
|
+
maxDelayMs: options.maxDelayMs,
|
|
5531
|
+
shouldRetry: options.shouldRetry
|
|
5532
|
+
} : void 0;
|
|
5533
|
+
let attempt = 0;
|
|
5534
|
+
while (true) {
|
|
5535
|
+
if (signal?.aborted) throw new Error("Speech synthesis was cancelled.");
|
|
5536
|
+
try {
|
|
5537
|
+
return await synthesize();
|
|
5538
|
+
} catch (error) {
|
|
5539
|
+
if (!retry || attempt >= retry.maxRetries || !(retry.shouldRetry?.(error, attempt + 1) ?? isRetryable(error)))
|
|
5540
|
+
throw error;
|
|
5541
|
+
attempt += 1;
|
|
5542
|
+
const delayMs = retryDelayForError(retry, attempt, error);
|
|
5543
|
+
onRetry(attempt, delayMs);
|
|
5544
|
+
if (delayMs > 0)
|
|
5545
|
+
await new Promise((resolve, reject) => {
|
|
5546
|
+
const timer = setTimeout(() => {
|
|
5547
|
+
signal?.removeEventListener("abort", abort);
|
|
5548
|
+
resolve();
|
|
5549
|
+
}, delayMs);
|
|
5550
|
+
const abort = () => {
|
|
5551
|
+
clearTimeout(timer);
|
|
5552
|
+
signal?.removeEventListener("abort", abort);
|
|
5553
|
+
reject(new Error("Speech synthesis was cancelled."));
|
|
5554
|
+
};
|
|
5555
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
5556
|
+
});
|
|
5557
|
+
}
|
|
5558
|
+
}
|
|
5559
|
+
}
|
|
5560
|
+
function sharedValidationOptions(options, signal) {
|
|
5561
|
+
const validator = options.urlValidator ?? options.customUrlValidator;
|
|
5562
|
+
if (!validator) return signal ? withValidationSignal(options, signal) : options;
|
|
5563
|
+
const runner = createAzureUrlValidatorRunner2(validator, {
|
|
5564
|
+
...options.urlValidation ?? {},
|
|
5565
|
+
...options.urlValidatorConcurrency !== void 0 ? { concurrency: options.urlValidatorConcurrency } : {},
|
|
5566
|
+
...options.timeouts?.urlValidationMs !== void 0 ? { timeoutMs: options.timeouts.urlValidationMs } : options.urlValidatorTimeoutMs !== void 0 ? { timeoutMs: options.urlValidatorTimeoutMs } : {},
|
|
5567
|
+
...signal ? { signal } : {},
|
|
5568
|
+
...options.urlValidatorCache ? { cache: options.urlValidatorCache } : {}
|
|
5569
|
+
});
|
|
5570
|
+
return {
|
|
5571
|
+
...withValidationSignal(options, signal),
|
|
5572
|
+
urlValidatorRunner: runner
|
|
5573
|
+
};
|
|
4996
5574
|
}
|
|
4997
5575
|
async function synthesizeSsmlSafe(client, ssml, options = {}) {
|
|
4998
|
-
const validationOptions =
|
|
5576
|
+
const validationOptions = sharedValidationOptions(options.validation ?? options, options.signal);
|
|
4999
5577
|
const diagnostics = await Promise.resolve(validateAzureSsml2(ssml, validationOptions));
|
|
5000
5578
|
if (options.signal?.aborted) {
|
|
5001
5579
|
const error = toSynthesisError(new Error("Speech synthesis was cancelled."));
|
|
@@ -5014,7 +5592,11 @@ async function synthesizeSsmlSafe(client, ssml, options = {}) {
|
|
|
5014
5592
|
ok: true,
|
|
5015
5593
|
success: true,
|
|
5016
5594
|
status: "success",
|
|
5017
|
-
value: await client.synthesizeSsml(ssml, {
|
|
5595
|
+
value: await client.synthesizeSsml(ssml, {
|
|
5596
|
+
signal: options.signal,
|
|
5597
|
+
timeoutMs: options.timeouts?.perChunkMs,
|
|
5598
|
+
timeouts: options.timeouts
|
|
5599
|
+
})
|
|
5018
5600
|
};
|
|
5019
5601
|
} catch (error) {
|
|
5020
5602
|
const synthesisError = toSynthesisError(error);
|
|
@@ -5022,7 +5604,10 @@ async function synthesizeSsmlSafe(client, ssml, options = {}) {
|
|
|
5022
5604
|
}
|
|
5023
5605
|
}
|
|
5024
5606
|
async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
5025
|
-
const validationOptions =
|
|
5607
|
+
const validationOptions = sharedValidationOptions(
|
|
5608
|
+
{ ...options.validation ?? options, timeouts: options.timeouts },
|
|
5609
|
+
options.signal
|
|
5610
|
+
);
|
|
5026
5611
|
if (options.signal?.aborted) {
|
|
5027
5612
|
const error = toSynthesisError(new Error("Speech synthesis was cancelled."));
|
|
5028
5613
|
return failure(error);
|
|
@@ -5043,21 +5628,30 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
5043
5628
|
pending(index, "pending");
|
|
5044
5629
|
});
|
|
5045
5630
|
const validations = await Promise.all(
|
|
5046
|
-
chunks.map(async (chunk) => {
|
|
5631
|
+
chunks.map(async (chunk, index) => {
|
|
5047
5632
|
const ssml = typeof chunk === "string" ? chunk : chunk.ssml;
|
|
5048
5633
|
const sourceNodePath = typeof chunk === "string" ? options.sourceNodePath : chunk.sourceNodePath ?? options.sourceNodePath;
|
|
5049
5634
|
const diagnostics = await Promise.resolve(
|
|
5050
|
-
validateAzureSsml2(ssml, {
|
|
5635
|
+
validateAzureSsml2(ssml, {
|
|
5636
|
+
...validationOptions,
|
|
5637
|
+
...sourceNodePath ? { sourceNodePath } : {},
|
|
5638
|
+
chunkIndex: index
|
|
5639
|
+
})
|
|
5051
5640
|
);
|
|
5052
5641
|
return diagnostics.filter((diagnostic) => diagnostic.severity === "error");
|
|
5053
5642
|
})
|
|
5054
5643
|
);
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5644
|
+
if (options.signal?.aborted) {
|
|
5645
|
+
const error = toSynthesisError(new Error("Speech synthesis was cancelled."));
|
|
5646
|
+
return failure(error);
|
|
5647
|
+
}
|
|
5648
|
+
const chunkDiagnostics = validations.map((diagnostics, chunkIndex) => ({ chunkIndex, diagnostics })).filter((entry) => entry.diagnostics.length > 0);
|
|
5649
|
+
if (chunkDiagnostics.length > 0) {
|
|
5650
|
+
const error = new BatchChunkValidationError(chunkDiagnostics);
|
|
5651
|
+
for (const entry of chunkDiagnostics) pending(entry.chunkIndex, "failed", error);
|
|
5059
5652
|
return failure(error);
|
|
5060
5653
|
}
|
|
5654
|
+
let fallbackJobScope;
|
|
5061
5655
|
try {
|
|
5062
5656
|
if (client.synthesizeChunks) {
|
|
5063
5657
|
const normalizedChunks = chunks.map((chunk) => {
|
|
@@ -5069,101 +5663,181 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
5069
5663
|
outputFormat: options.outputFormat,
|
|
5070
5664
|
signal: options.signal,
|
|
5071
5665
|
timeoutMs: options.timeoutMs,
|
|
5072
|
-
|
|
5666
|
+
timeouts: options.timeouts,
|
|
5667
|
+
sourceNodePath: options.sourceNodePath,
|
|
5668
|
+
concurrency: options.concurrency,
|
|
5669
|
+
retryOptions: options.retryOptions,
|
|
5670
|
+
cancelOnFailure: options.cancelOnFailure,
|
|
5671
|
+
resumeChunks: options.resumeChunks,
|
|
5672
|
+
resumeChunkIndices: options.resumeChunkIndices,
|
|
5673
|
+
customMerger: options.customMerger,
|
|
5674
|
+
outputMimeType: options.outputMimeType,
|
|
5675
|
+
postMergeValidator: options.postMergeValidator
|
|
5073
5676
|
});
|
|
5074
5677
|
return { ok: true, success: true, status: "success", value };
|
|
5075
5678
|
}
|
|
5076
|
-
const results =
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5679
|
+
const results = new Array(chunks.length);
|
|
5680
|
+
const cachedChunks = new Map((options.resumeChunks ?? []).map((chunk) => [chunk.chunkIndex, chunk]));
|
|
5681
|
+
for (const [index, cached] of cachedChunks) {
|
|
5682
|
+
if (index >= 0 && index < chunks.length) results[index] = cached;
|
|
5683
|
+
}
|
|
5684
|
+
const requestedIndices = options.resumeChunkIndices ? new Set(options.resumeChunkIndices.filter((index) => index >= 0 && index < chunks.length)) : void 0;
|
|
5685
|
+
const shouldSynthesize = (index) => !cachedChunks.has(index) && (requestedIndices === void 0 || requestedIndices.has(index));
|
|
5686
|
+
const jobScope = chunks.length > 1 || options.timeouts?.totalJobMs !== void 0 ? createSafeAbortScope(options.signal, options.timeouts?.totalJobMs) : void 0;
|
|
5687
|
+
fallbackJobScope = jobScope;
|
|
5688
|
+
const failedIndices = /* @__PURE__ */ new Set();
|
|
5689
|
+
let firstError;
|
|
5690
|
+
let completed = [...results].filter((result) => result !== void 0).length;
|
|
5691
|
+
let nextIndex = 0;
|
|
5692
|
+
const concurrency = resolveConcurrency2(options.concurrency, chunks.length);
|
|
5693
|
+
const worker = async () => {
|
|
5694
|
+
while (true) {
|
|
5695
|
+
const index = nextIndex++;
|
|
5696
|
+
if (index >= chunks.length) return;
|
|
5697
|
+
if (!shouldSynthesize(index)) continue;
|
|
5698
|
+
if (failedIndices.size > 0 && options.cancelOnFailure !== false) return;
|
|
5699
|
+
const chunk = chunks[index];
|
|
5700
|
+
const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
|
|
5701
|
+
const sourceNodePath = input.sourceNodePath;
|
|
5702
|
+
const originalTextRange = input.originalTextRange;
|
|
5703
|
+
pending(index, "synthesizing");
|
|
5704
|
+
const startedAt = Date.now();
|
|
5705
|
+
try {
|
|
5706
|
+
const chunkTimeout = options.timeouts?.chunkWithRetriesMs ?? options.timeouts?.perChunkMs;
|
|
5707
|
+
const chunkScope = chunkTimeout !== void 0 || jobScope ? createSafeAbortScope(jobScope?.signal ?? options.signal, chunkTimeout ?? options.timeoutMs) : void 0;
|
|
5708
|
+
const chunkSignal = chunkScope?.signal ?? options.signal;
|
|
5709
|
+
let result;
|
|
5710
|
+
try {
|
|
5711
|
+
result = await retryableSynthesis(
|
|
5712
|
+
() => client.synthesizeSsml(input.ssml, {
|
|
5713
|
+
outputFormat: options.outputFormat,
|
|
5714
|
+
signal: chunkSignal,
|
|
5715
|
+
timeoutMs: options.timeouts?.perChunkMs ?? options.timeoutMs,
|
|
5716
|
+
sourceNodePath: input.sourceNodePath ?? options.sourceNodePath
|
|
5717
|
+
}),
|
|
5718
|
+
options.retryOptions,
|
|
5719
|
+
chunkSignal,
|
|
5720
|
+
(retryAttempt, nextRetryDelayMs) => options.onProgress?.({
|
|
5721
|
+
currentChunk: completed,
|
|
5722
|
+
totalChunks: chunks.length,
|
|
5723
|
+
percent: chunks.length === 0 ? 100 : Math.round(completed / chunks.length * 100),
|
|
5724
|
+
chunkIndex: index,
|
|
5725
|
+
originalTextRange: input.originalTextRange,
|
|
5726
|
+
status: "synthesizing",
|
|
5727
|
+
durationMs: Date.now() - startedAt,
|
|
5728
|
+
retryAttempt,
|
|
5729
|
+
nextRetryDelayMs,
|
|
5730
|
+
isRetrying: true
|
|
5731
|
+
})
|
|
5732
|
+
);
|
|
5733
|
+
} catch (error) {
|
|
5734
|
+
if (chunkScope?.timedOut())
|
|
5735
|
+
throw new Error(`Speech synthesis timed out after ${chunkTimeout ?? options.timeoutMs} ms.`);
|
|
5736
|
+
throw error;
|
|
5737
|
+
} finally {
|
|
5738
|
+
chunkScope?.dispose();
|
|
5739
|
+
}
|
|
5740
|
+
results[index] = {
|
|
5741
|
+
...result,
|
|
5742
|
+
...input.originalTextRange ? { textRange: { ...input.originalTextRange } } : {},
|
|
5743
|
+
...sourceNodePath ? {
|
|
5744
|
+
boundaries: result.boundaries?.map((event) => ({
|
|
5745
|
+
...event,
|
|
5746
|
+
sourceNodePath: [...sourceNodePath],
|
|
5747
|
+
...event.originalTextRange ? { originalTextRange: { ...event.originalTextRange } } : input.originalTextRange ? { originalTextRange: { ...input.originalTextRange } } : {}
|
|
5748
|
+
})),
|
|
5749
|
+
visemes: result.visemes?.map((event) => ({
|
|
5750
|
+
...event,
|
|
5751
|
+
sourceNodePath: [...sourceNodePath],
|
|
5752
|
+
...event.originalTextRange ? { originalTextRange: { ...event.originalTextRange } } : input.originalTextRange ? { originalTextRange: { ...input.originalTextRange } } : {}
|
|
5753
|
+
})),
|
|
5754
|
+
bookmarks: result.bookmarks?.map((event) => ({
|
|
5755
|
+
...event,
|
|
5756
|
+
sourceNodePath: [...sourceNodePath],
|
|
5757
|
+
...event.originalTextRange ? { originalTextRange: { ...event.originalTextRange } } : input.originalTextRange ? { originalTextRange: { ...input.originalTextRange } } : {}
|
|
5758
|
+
}))
|
|
5759
|
+
} : {},
|
|
5760
|
+
...originalTextRange ? {
|
|
5761
|
+
boundaries: result.boundaries?.map((event) => ({
|
|
5762
|
+
...event,
|
|
5763
|
+
originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
|
|
5764
|
+
})),
|
|
5765
|
+
wordBoundary: result.wordBoundary?.map((event) => ({
|
|
5766
|
+
...event,
|
|
5767
|
+
originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
|
|
5768
|
+
})),
|
|
5769
|
+
wordBoundaries: result.wordBoundaries?.map((event) => ({
|
|
5770
|
+
...event,
|
|
5771
|
+
originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
|
|
5772
|
+
})),
|
|
5773
|
+
visemes: result.visemes?.map((event) => ({
|
|
5774
|
+
...event,
|
|
5775
|
+
originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
|
|
5776
|
+
})),
|
|
5777
|
+
bookmarks: result.bookmarks?.map((event) => ({
|
|
5778
|
+
...event,
|
|
5779
|
+
originalTextRange: event.originalTextRange ? { ...event.originalTextRange } : { ...originalTextRange }
|
|
5780
|
+
}))
|
|
5781
|
+
} : {}
|
|
5782
|
+
};
|
|
5783
|
+
completed += 1;
|
|
5784
|
+
options.onProgress?.({
|
|
5785
|
+
currentChunk: completed,
|
|
5786
|
+
totalChunks: chunks.length,
|
|
5787
|
+
percent: chunks.length === 0 ? 100 : Math.round(completed / chunks.length * 100),
|
|
5788
|
+
chunkIndex: index,
|
|
5789
|
+
originalTextRange: input.originalTextRange,
|
|
5790
|
+
status: "success",
|
|
5791
|
+
durationMs: Date.now() - startedAt
|
|
5792
|
+
});
|
|
5793
|
+
} catch (error) {
|
|
5794
|
+
failedIndices.add(index);
|
|
5795
|
+
options.onProgress?.({
|
|
5796
|
+
currentChunk: completed,
|
|
5797
|
+
totalChunks: chunks.length,
|
|
5798
|
+
percent: chunks.length === 0 ? 100 : Math.round(index / chunks.length * 100),
|
|
5799
|
+
chunkIndex: index,
|
|
5800
|
+
originalTextRange: input.originalTextRange,
|
|
5801
|
+
status: "failed",
|
|
5802
|
+
durationMs: Date.now() - startedAt,
|
|
5803
|
+
error
|
|
5804
|
+
});
|
|
5805
|
+
if (options.cancelOnFailure !== false) jobScope?.abort();
|
|
5806
|
+
firstError ?? (firstError = error);
|
|
5807
|
+
return;
|
|
5808
|
+
}
|
|
5154
5809
|
}
|
|
5810
|
+
};
|
|
5811
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, chunks.length) }, () => worker()));
|
|
5812
|
+
if (failedIndices.size > 0) {
|
|
5813
|
+
const error = firstError ?? new Error("One or more SSML chunks failed to synthesize.");
|
|
5814
|
+
error.partialResult = {
|
|
5815
|
+
synthesizedChunks: results.flatMap((result, chunkIndex) => result ? [{ ...result, chunkIndex }] : []),
|
|
5816
|
+
completedChunks: results.flatMap((result, chunkIndex) => result ? [{ ...result, chunkIndex }] : []),
|
|
5817
|
+
pendingChunkIndices: chunks.flatMap((_chunk, chunkIndex) => results[chunkIndex] ? [] : [chunkIndex]),
|
|
5818
|
+
failedChunkIndices: [...failedIndices],
|
|
5819
|
+
totalChunks: chunks.length
|
|
5820
|
+
};
|
|
5821
|
+
throw error;
|
|
5155
5822
|
}
|
|
5823
|
+
const orderedResults = results.filter((result) => result !== void 0);
|
|
5156
5824
|
return {
|
|
5157
5825
|
ok: true,
|
|
5158
5826
|
success: true,
|
|
5159
5827
|
status: "success",
|
|
5160
|
-
value: mergeSynthesisResults(
|
|
5161
|
-
format: options.outputFormat ?? "audio-16khz-128kbitrate-mono-mp3"
|
|
5828
|
+
value: await mergeSynthesisResults(orderedResults, {
|
|
5829
|
+
format: options.outputFormat ?? "audio-16khz-128kbitrate-mono-mp3",
|
|
5830
|
+
signal: jobScope?.signal ?? options.signal,
|
|
5831
|
+
customMerger: options.customMerger,
|
|
5832
|
+
outputMimeType: options.outputMimeType,
|
|
5833
|
+
postMergeValidator: options.postMergeValidator
|
|
5162
5834
|
})
|
|
5163
5835
|
};
|
|
5164
5836
|
} catch (error) {
|
|
5165
5837
|
const synthesisError = toSynthesisError(error);
|
|
5166
|
-
return failure(synthesisError);
|
|
5838
|
+
return failure(synthesisError, partialResultFrom(error));
|
|
5839
|
+
} finally {
|
|
5840
|
+
fallbackJobScope?.dispose();
|
|
5167
5841
|
}
|
|
5168
5842
|
}
|
|
5169
5843
|
function withValidationSignal(options, signal) {
|
|
@@ -5184,14 +5858,14 @@ var AzureTtsClient = class {
|
|
|
5184
5858
|
__privateSet(this, _options, options);
|
|
5185
5859
|
}
|
|
5186
5860
|
async synthesize(ssml) {
|
|
5187
|
-
const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
|
|
5861
|
+
const { region, subscriptionKey, outputFormat, signal, timeoutMs, timeouts } = __privateGet(this, _options);
|
|
5188
5862
|
const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
5189
5863
|
__privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
|
|
5190
|
-
const config = { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs };
|
|
5864
|
+
const config = { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs, timeouts };
|
|
5191
5865
|
return synthesizeSpeech(ssml, config);
|
|
5192
5866
|
}
|
|
5193
5867
|
async synthesizeSsml(ssml, options = {}) {
|
|
5194
|
-
const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
|
|
5868
|
+
const { region, subscriptionKey, outputFormat, signal, timeoutMs, timeouts } = __privateGet(this, _options);
|
|
5195
5869
|
const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
5196
5870
|
__privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
|
|
5197
5871
|
return synthesizeSsml(ssml, {
|
|
@@ -5201,13 +5875,14 @@ var AzureTtsClient = class {
|
|
|
5201
5875
|
outputFormat: options.outputFormat ?? outputFormat,
|
|
5202
5876
|
signal: options.signal ?? signal,
|
|
5203
5877
|
timeoutMs: options.timeoutMs ?? timeoutMs,
|
|
5878
|
+
timeouts: options.timeouts ?? timeouts,
|
|
5204
5879
|
sourceNodePath: options.sourceNodePath,
|
|
5205
5880
|
sourceTextSegments: options.sourceTextSegments,
|
|
5206
5881
|
sourceMarkers: options.sourceMarkers
|
|
5207
5882
|
});
|
|
5208
5883
|
}
|
|
5209
5884
|
async synthesizeChunks(chunks, options = {}) {
|
|
5210
|
-
const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
|
|
5885
|
+
const { region, subscriptionKey, outputFormat, signal, timeoutMs, timeouts } = __privateGet(this, _options);
|
|
5211
5886
|
const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
5212
5887
|
return synthesizeSsmlChunks(chunks, {
|
|
5213
5888
|
endpoint,
|
|
@@ -5216,8 +5891,17 @@ var AzureTtsClient = class {
|
|
|
5216
5891
|
outputFormat: options.outputFormat ?? outputFormat,
|
|
5217
5892
|
signal: options.signal ?? signal,
|
|
5218
5893
|
timeoutMs: options.timeoutMs ?? timeoutMs,
|
|
5894
|
+
timeouts: options.timeouts ?? timeouts,
|
|
5219
5895
|
sourceNodePath: options.sourceNodePath,
|
|
5220
|
-
onProgress: options.onProgress ?? __privateGet(this, _options).onProgress
|
|
5896
|
+
onProgress: options.onProgress ?? __privateGet(this, _options).onProgress,
|
|
5897
|
+
concurrency: options.concurrency ?? __privateGet(this, _options).concurrency,
|
|
5898
|
+
retryOptions: options.retryOptions ?? __privateGet(this, _options).retryOptions,
|
|
5899
|
+
cancelOnFailure: options.cancelOnFailure,
|
|
5900
|
+
resumeChunks: options.resumeChunks,
|
|
5901
|
+
resumeChunkIndices: options.resumeChunkIndices,
|
|
5902
|
+
customMerger: options.customMerger,
|
|
5903
|
+
outputMimeType: options.outputMimeType,
|
|
5904
|
+
postMergeValidator: options.postMergeValidator
|
|
5221
5905
|
});
|
|
5222
5906
|
}
|
|
5223
5907
|
async synthesizeSsmlSafe(ssml, options = {}) {
|
|
@@ -5229,7 +5913,10 @@ var AzureTtsClient = class {
|
|
|
5229
5913
|
outputFormat: options.outputFormat ?? __privateGet(this, _options).outputFormat,
|
|
5230
5914
|
signal: options.signal ?? __privateGet(this, _options).signal,
|
|
5231
5915
|
timeoutMs: options.timeoutMs ?? __privateGet(this, _options).timeoutMs,
|
|
5232
|
-
|
|
5916
|
+
timeouts: options.timeouts ?? __privateGet(this, _options).timeouts,
|
|
5917
|
+
onProgress: options.onProgress ?? __privateGet(this, _options).onProgress,
|
|
5918
|
+
concurrency: options.concurrency ?? __privateGet(this, _options).concurrency,
|
|
5919
|
+
retryOptions: options.retryOptions ?? __privateGet(this, _options).retryOptions
|
|
5233
5920
|
});
|
|
5234
5921
|
}
|
|
5235
5922
|
async synthesizeSsmlChunksSafe(chunks, options = {}) {
|
|
@@ -5320,15 +6007,19 @@ async function fetchAzureVoiceCatalog(options) {
|
|
|
5320
6007
|
voiceCount: sortedVoices.length,
|
|
5321
6008
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5322
6009
|
apiVersion: AZURE_VOICE_API_VERSION,
|
|
5323
|
-
regions
|
|
6010
|
+
regions,
|
|
6011
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3).toISOString(),
|
|
6012
|
+
regionDiffs: {}
|
|
5324
6013
|
}
|
|
5325
6014
|
};
|
|
5326
6015
|
}
|
|
5327
6016
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5328
6017
|
0 && (module.exports = {
|
|
6018
|
+
AudioFormatMismatchError,
|
|
5329
6019
|
AzureTtsClient,
|
|
5330
6020
|
AzureTtsError,
|
|
5331
6021
|
AzureTtsSdkError,
|
|
6022
|
+
BatchChunkValidationError,
|
|
5332
6023
|
ChunkValidationError,
|
|
5333
6024
|
DEFAULT_OUTPUT_FORMAT,
|
|
5334
6025
|
MergeError,
|
|
@@ -5346,7 +6037,9 @@ async function fetchAzureVoiceCatalog(options) {
|
|
|
5346
6037
|
fromPlainTextToSsml,
|
|
5347
6038
|
getAzureVoiceCatalogMetadata,
|
|
5348
6039
|
getBuiltInVoiceCatalogMetadata,
|
|
6040
|
+
getRetryAfterDelayMs,
|
|
5349
6041
|
getSsmlSourceMap,
|
|
6042
|
+
inspectAudioSpecification,
|
|
5350
6043
|
isValidAzureAudioDuration,
|
|
5351
6044
|
mapSsmlTextNodes,
|
|
5352
6045
|
mergeAudioBuffers,
|
|
@@ -5362,6 +6055,7 @@ async function fetchAzureVoiceCatalog(options) {
|
|
|
5362
6055
|
synthesizeSsmlChunksSafe,
|
|
5363
6056
|
synthesizeSsmlSafe,
|
|
5364
6057
|
validateAzureSsml,
|
|
6058
|
+
validateAzureSsmlChunks,
|
|
5365
6059
|
validateSsml,
|
|
5366
6060
|
validateSsmlStructureIntegrity
|
|
5367
6061
|
});
|