ssml-builder-js 2.18.0 → 2.19.1
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 +4 -0
- package/dist/{index.d-B2WddTa4.d.mts → index.d-4kqVuH29.d.mts} +1 -1
- package/dist/{index.d-B2WddTa4.d.ts → index.d-4kqVuH29.d.ts} +1 -1
- package/dist/index.d.mts +156 -105
- package/dist/index.d.ts +156 -105
- package/dist/index.js +453 -63
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +450 -63
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +45 -24
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +45 -24
- package/dist/react.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -44,6 +44,8 @@ __export(src_exports, {
|
|
|
44
44
|
BatchChunkValidationError: () => BatchChunkValidationError,
|
|
45
45
|
ChunkValidationError: () => ChunkValidationError,
|
|
46
46
|
DEFAULT_OUTPUT_FORMAT: () => DEFAULT_OUTPUT_FORMAT,
|
|
47
|
+
DeadlineController: () => DeadlineController,
|
|
48
|
+
IncompleteChunkSetError: () => IncompleteChunkSetError,
|
|
47
49
|
MergeError: () => MergeError,
|
|
48
50
|
SynthesisCancelledError: () => SynthesisCancelledError,
|
|
49
51
|
SynthesisTimeoutError: () => SynthesisTimeoutError,
|
|
@@ -71,6 +73,7 @@ __export(src_exports, {
|
|
|
71
73
|
parseSsml: () => parseSsml,
|
|
72
74
|
resolveMergeAudioFormat: () => resolveMergeAudioFormat,
|
|
73
75
|
resolveMimeType: () => resolveMimeType,
|
|
76
|
+
serializeChunkError: () => serializeChunkError,
|
|
74
77
|
splitSsmlDocument: () => splitSsmlDocument,
|
|
75
78
|
synthesizeSpeech: () => synthesizeSpeech,
|
|
76
79
|
synthesizeSsml: () => synthesizeSsml,
|
|
@@ -2803,6 +2806,15 @@ var SynthesisTimeoutError = class extends Error {
|
|
|
2803
2806
|
this.name = "SynthesisTimeoutError";
|
|
2804
2807
|
}
|
|
2805
2808
|
};
|
|
2809
|
+
var IncompleteChunkSetError = class extends Error {
|
|
2810
|
+
constructor(totalChunks, missingChunkIndices) {
|
|
2811
|
+
super(`Cannot merge an incomplete chunk set; missing chunk indices: ${missingChunkIndices.join(", ")}.`);
|
|
2812
|
+
this.kind = "incomplete-chunk-set";
|
|
2813
|
+
this.name = "IncompleteChunkSetError";
|
|
2814
|
+
this.totalChunks = totalChunks;
|
|
2815
|
+
this.missingChunkIndices = [...missingChunkIndices];
|
|
2816
|
+
}
|
|
2817
|
+
};
|
|
2806
2818
|
var MergeError = class extends Error {
|
|
2807
2819
|
constructor(message, cause) {
|
|
2808
2820
|
super(message);
|
|
@@ -2827,8 +2839,32 @@ var UnsupportedMergeFormatError = class extends Error {
|
|
|
2827
2839
|
this.format = format;
|
|
2828
2840
|
}
|
|
2829
2841
|
};
|
|
2842
|
+
function serializeChunkError(error, phase, isOriginalFailure) {
|
|
2843
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2844
|
+
const status = error instanceof AzureTtsError ? error.status : void 0;
|
|
2845
|
+
const kind = error && typeof error === "object" && "kind" in error ? String(error.kind) : "";
|
|
2846
|
+
const code = kind === "validation-error" ? "VALIDATION_ERROR" : kind === "timeout" || /tim(?:e|ed) ?out|deadline/i.test(message) ? "TIMEOUT" : kind === "cancelled" || /cancel|abort/i.test(message) ? "CANCELLED" : kind === "audio-format-mismatch" || kind === "unsupported-format-error" ? "FORMAT_MISMATCH" : kind === "merge-error" || phase === "merge" ? "MERGE_ERROR" : "AZURE_API_ERROR";
|
|
2847
|
+
const details = {};
|
|
2848
|
+
if (error instanceof AzureTtsError) {
|
|
2849
|
+
details.statusText = error.statusText;
|
|
2850
|
+
if (error.requestId) details.requestId = error.requestId;
|
|
2851
|
+
}
|
|
2852
|
+
if (error instanceof IncompleteChunkSetError) {
|
|
2853
|
+
details.totalChunks = error.totalChunks;
|
|
2854
|
+
details.missingChunkIndices = [...error.missingChunkIndices];
|
|
2855
|
+
}
|
|
2856
|
+
return {
|
|
2857
|
+
code,
|
|
2858
|
+
phase,
|
|
2859
|
+
message,
|
|
2860
|
+
isOriginalFailure,
|
|
2861
|
+
isRetryable: code === "AZURE_API_ERROR" && (status === 429 || status !== void 0 && status >= 500 || /network|connection|connect|socket|fetch failed|econn|etimedout|temporar|transient|unavailable/i.test(message)),
|
|
2862
|
+
...status !== void 0 && status > 0 ? { httpStatus: status } : {},
|
|
2863
|
+
...Object.keys(details).length > 0 ? { details } : {}
|
|
2864
|
+
};
|
|
2865
|
+
}
|
|
2830
2866
|
function toSynthesisError(error) {
|
|
2831
|
-
if (error instanceof AzureTtsError || error instanceof MergeError || error instanceof AudioFormatMismatchError || error instanceof UnsupportedMergeFormatError || error instanceof SynthesisCancelledError || error instanceof SynthesisTimeoutError)
|
|
2867
|
+
if (error instanceof AzureTtsError || error instanceof MergeError || error instanceof AudioFormatMismatchError || error instanceof UnsupportedMergeFormatError || error instanceof SynthesisCancelledError || error instanceof SynthesisTimeoutError || error instanceof IncompleteChunkSetError)
|
|
2832
2868
|
return error;
|
|
2833
2869
|
const message = error instanceof Error ? error.message : String(error);
|
|
2834
2870
|
if (/cancel|abort/i.test(message)) return new SynthesisCancelledError(message);
|
|
@@ -2840,6 +2876,55 @@ function createSpeechSdkError(error) {
|
|
|
2840
2876
|
return new AzureTtsSdkError(message);
|
|
2841
2877
|
}
|
|
2842
2878
|
|
|
2879
|
+
// packages/azure-tts-client/src/deadline.ts
|
|
2880
|
+
var _controller, _parent, _onParentAbort, _timer, _timedOut;
|
|
2881
|
+
var DeadlineController = class {
|
|
2882
|
+
constructor(totalJobMs, parent) {
|
|
2883
|
+
__privateAdd(this, _controller, new AbortController());
|
|
2884
|
+
__privateAdd(this, _parent);
|
|
2885
|
+
__privateAdd(this, _onParentAbort);
|
|
2886
|
+
__privateAdd(this, _timer);
|
|
2887
|
+
__privateAdd(this, _timedOut, false);
|
|
2888
|
+
__privateSet(this, _parent, parent);
|
|
2889
|
+
__privateSet(this, _onParentAbort, () => __privateGet(this, _controller).abort());
|
|
2890
|
+
this.deadlineAtMs = totalJobMs !== void 0 && totalJobMs > 0 ? Date.now() + totalJobMs : void 0;
|
|
2891
|
+
this.signal = this.deadlineAtMs === void 0 && parent ? parent : __privateGet(this, _controller).signal;
|
|
2892
|
+
if (parent?.aborted) __privateGet(this, _controller).abort();
|
|
2893
|
+
parent?.addEventListener("abort", __privateGet(this, _onParentAbort), { once: true });
|
|
2894
|
+
if (this.deadlineAtMs !== void 0) {
|
|
2895
|
+
__privateSet(this, _timer, setTimeout(
|
|
2896
|
+
() => {
|
|
2897
|
+
__privateSet(this, _timedOut, true);
|
|
2898
|
+
__privateGet(this, _controller).abort();
|
|
2899
|
+
},
|
|
2900
|
+
Math.max(0, this.deadlineAtMs - Date.now())
|
|
2901
|
+
));
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
get timedOut() {
|
|
2905
|
+
return __privateGet(this, _timedOut) || this.deadlineAtMs !== void 0 && this.remainingMs <= 0;
|
|
2906
|
+
}
|
|
2907
|
+
get remainingMs() {
|
|
2908
|
+
return this.deadlineAtMs === void 0 ? Number.POSITIVE_INFINITY : Math.max(0, this.deadlineAtMs - Date.now());
|
|
2909
|
+
}
|
|
2910
|
+
throwIfExpired() {
|
|
2911
|
+
if (this.timedOut) throw new SynthesisTimeoutError("Speech synthesis exceeded the total job deadline.");
|
|
2912
|
+
if (this.signal.aborted) throw new Error("Speech synthesis was cancelled.");
|
|
2913
|
+
}
|
|
2914
|
+
abort() {
|
|
2915
|
+
__privateGet(this, _controller).abort();
|
|
2916
|
+
}
|
|
2917
|
+
dispose() {
|
|
2918
|
+
if (__privateGet(this, _timer)) clearTimeout(__privateGet(this, _timer));
|
|
2919
|
+
__privateGet(this, _parent)?.removeEventListener("abort", __privateGet(this, _onParentAbort));
|
|
2920
|
+
}
|
|
2921
|
+
};
|
|
2922
|
+
_controller = new WeakMap();
|
|
2923
|
+
_parent = new WeakMap();
|
|
2924
|
+
_onParentAbort = new WeakMap();
|
|
2925
|
+
_timer = new WeakMap();
|
|
2926
|
+
_timedOut = new WeakMap();
|
|
2927
|
+
|
|
2843
2928
|
// packages/azure-tts-client/src/synthesis.ts
|
|
2844
2929
|
var SpeechSDK2 = __toESM(require("microsoft-cognitiveservices-speech-sdk"));
|
|
2845
2930
|
|
|
@@ -4615,18 +4700,23 @@ function createSpeechConfig(config) {
|
|
|
4615
4700
|
}
|
|
4616
4701
|
|
|
4617
4702
|
// packages/azure-tts-client/src/synthesis.ts
|
|
4618
|
-
function computeChunkFingerprint(ssml, outputFormat = DEFAULT_OUTPUT_FORMAT) {
|
|
4703
|
+
function computeChunkFingerprint(ssml, outputFormat = DEFAULT_OUTPUT_FORMAT, options = {}) {
|
|
4619
4704
|
const readAttribute3 = (name) => {
|
|
4620
4705
|
const pattern = new RegExp(`(?:${name})\\s*=\\s*[\\"']([^\\"']*)`, "gi");
|
|
4621
4706
|
return [...ssml.matchAll(pattern)].map((match) => match[1] ?? "").join("|");
|
|
4622
4707
|
};
|
|
4708
|
+
const headers = Object.fromEntries(
|
|
4709
|
+
Object.entries(options.customHeaders ?? {}).sort(([first], [second]) => first.localeCompare(second))
|
|
4710
|
+
);
|
|
4623
4711
|
const payload = JSON.stringify({
|
|
4624
4712
|
ssml,
|
|
4625
4713
|
outputFormat,
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4714
|
+
region: options.region ?? "",
|
|
4715
|
+
endpoint: options.endpoint ?? "",
|
|
4716
|
+
voice: options.voice ?? readAttribute3("(?:name|voice)"),
|
|
4717
|
+
lang: options.lang ?? readAttribute3("(?:xml:lang|lang)"),
|
|
4718
|
+
customHeaders: headers,
|
|
4719
|
+
fingerprintSchemaVersion: options.fingerprintSchemaVersion ?? options.schemaVersion ?? "2"
|
|
4630
4720
|
});
|
|
4631
4721
|
let hash = 0xcbf29ce484222325n;
|
|
4632
4722
|
const mask = 0xffffffffffffffffn;
|
|
@@ -4746,6 +4836,129 @@ function parseMp3Specification(buffer, format) {
|
|
|
4746
4836
|
}
|
|
4747
4837
|
return void 0;
|
|
4748
4838
|
}
|
|
4839
|
+
function readEbmlVint(bytes, offset, preserveMarker) {
|
|
4840
|
+
const first = bytes[offset];
|
|
4841
|
+
if (first === void 0) throw new Error("Invalid EBML variable-length integer.");
|
|
4842
|
+
let mask = 128;
|
|
4843
|
+
let length = 1;
|
|
4844
|
+
while (length <= 8 && (first & mask) === 0) {
|
|
4845
|
+
mask >>= 1;
|
|
4846
|
+
length += 1;
|
|
4847
|
+
}
|
|
4848
|
+
if (length > 8 || offset + length > bytes.byteLength) throw new Error("Truncated EBML variable-length integer.");
|
|
4849
|
+
let value = preserveMarker ? first : first & mask - 1;
|
|
4850
|
+
for (let index = 1; index < length; index += 1) value = value * 256 + (bytes[offset + index] ?? 0);
|
|
4851
|
+
if (!preserveMarker && value === 2 ** (7 * length) - 1)
|
|
4852
|
+
throw new Error("EBML unknown-size elements are not supported.");
|
|
4853
|
+
return { value, length };
|
|
4854
|
+
}
|
|
4855
|
+
function readEbmlElement(bytes, offset) {
|
|
4856
|
+
const id = readEbmlVint(bytes, offset, true);
|
|
4857
|
+
const size = readEbmlVint(bytes, offset + id.length, false);
|
|
4858
|
+
const dataStart = offset + id.length + size.length;
|
|
4859
|
+
const dataEnd = dataStart + size.value;
|
|
4860
|
+
if (dataEnd > bytes.byteLength) throw new Error("EBML element exceeds the audio buffer.");
|
|
4861
|
+
return { id: id.value, dataStart, dataEnd };
|
|
4862
|
+
}
|
|
4863
|
+
function ebmlText(bytes, element) {
|
|
4864
|
+
return new TextDecoder().decode(bytes.slice(element.dataStart, element.dataEnd));
|
|
4865
|
+
}
|
|
4866
|
+
function findEbmlElement(bytes, start, end, id) {
|
|
4867
|
+
let offset = start;
|
|
4868
|
+
while (offset < end) {
|
|
4869
|
+
const element = readEbmlElement(bytes, offset);
|
|
4870
|
+
if (element.id === id) return element;
|
|
4871
|
+
offset = element.dataEnd;
|
|
4872
|
+
}
|
|
4873
|
+
if (offset !== end) throw new Error("Invalid EBML element boundary.");
|
|
4874
|
+
return void 0;
|
|
4875
|
+
}
|
|
4876
|
+
function parseOggSpecification(buffer, format) {
|
|
4877
|
+
const bytes = new Uint8Array(buffer);
|
|
4878
|
+
let offset = 0;
|
|
4879
|
+
let firstPayload;
|
|
4880
|
+
let pages = 0;
|
|
4881
|
+
while (offset < bytes.byteLength) {
|
|
4882
|
+
if (offset + 27 > bytes.byteLength || !ascii(bytes, offset, "OggS")) throw new Error("Invalid Ogg page header.");
|
|
4883
|
+
if (bytes[offset + 4] !== 0) throw new Error("Unsupported Ogg bitstream version.");
|
|
4884
|
+
const segmentCount = bytes[offset + 26] ?? 0;
|
|
4885
|
+
const lacingStart = offset + 27;
|
|
4886
|
+
const payloadStart = lacingStart + segmentCount;
|
|
4887
|
+
if (payloadStart > bytes.byteLength) throw new Error("Truncated Ogg segment table.");
|
|
4888
|
+
const payloadLength = bytes.slice(lacingStart, payloadStart).reduce((total, value) => total + value, 0);
|
|
4889
|
+
const pageEnd = payloadStart + payloadLength;
|
|
4890
|
+
if (pageEnd > bytes.byteLength) throw new Error("Ogg page payload exceeds the audio buffer.");
|
|
4891
|
+
if (pages === 0) firstPayload = bytes.slice(payloadStart, pageEnd);
|
|
4892
|
+
offset = pageEnd;
|
|
4893
|
+
pages += 1;
|
|
4894
|
+
}
|
|
4895
|
+
if (pages === 0 || !firstPayload || !ascii(firstPayload, 0, "OpusHead") || firstPayload.byteLength < 19)
|
|
4896
|
+
throw new Error("Ogg audio must contain a valid OpusHead packet.");
|
|
4897
|
+
const version = firstPayload[8];
|
|
4898
|
+
const channels = firstPayload[9] ?? 0;
|
|
4899
|
+
const sampleRate = new DataView(firstPayload.buffer, firstPayload.byteOffset, firstPayload.byteLength).getUint32(
|
|
4900
|
+
12,
|
|
4901
|
+
true
|
|
4902
|
+
);
|
|
4903
|
+
if (version !== 1 || channels <= 0 || sampleRate <= 0) throw new Error("Invalid Ogg OpusHead stream parameters.");
|
|
4904
|
+
return {
|
|
4905
|
+
format,
|
|
4906
|
+
mimeType: "audio/ogg",
|
|
4907
|
+
codec: "opus",
|
|
4908
|
+
sampleRate,
|
|
4909
|
+
channels,
|
|
4910
|
+
container: "ogg",
|
|
4911
|
+
isVbr: true,
|
|
4912
|
+
isCompressed: true
|
|
4913
|
+
};
|
|
4914
|
+
}
|
|
4915
|
+
function parseWebmSpecification(buffer, format) {
|
|
4916
|
+
const bytes = new Uint8Array(buffer);
|
|
4917
|
+
const ebml = readEbmlElement(bytes, 0);
|
|
4918
|
+
if (ebml.id !== 440786851) throw new Error("WebM audio must begin with an EBML header.");
|
|
4919
|
+
const docType = findEbmlElement(bytes, ebml.dataStart, ebml.dataEnd, 17026);
|
|
4920
|
+
if (!docType || ebmlText(bytes, docType).toLowerCase() !== "webm") throw new Error("EBML DocType must be webm.");
|
|
4921
|
+
const segment = readEbmlElement(bytes, ebml.dataEnd);
|
|
4922
|
+
if (segment.id !== 408125543) throw new Error("WebM audio must contain a Segment element.");
|
|
4923
|
+
const tracks = findEbmlElement(bytes, segment.dataStart, segment.dataEnd, 374648427);
|
|
4924
|
+
if (!tracks) throw new Error("WebM audio must contain a Tracks element.");
|
|
4925
|
+
let offset = tracks.dataStart;
|
|
4926
|
+
let opusTrack;
|
|
4927
|
+
while (offset < tracks.dataEnd) {
|
|
4928
|
+
const track = readEbmlElement(bytes, offset);
|
|
4929
|
+
if (track.id === 174) {
|
|
4930
|
+
const codec = findEbmlElement(bytes, track.dataStart, track.dataEnd, 134);
|
|
4931
|
+
const trackType = findEbmlElement(bytes, track.dataStart, track.dataEnd, 131);
|
|
4932
|
+
if (codec && ebmlText(bytes, codec) === "A_OPUS" && trackType && bytes[trackType.dataStart] === 2) {
|
|
4933
|
+
opusTrack = track;
|
|
4934
|
+
break;
|
|
4935
|
+
}
|
|
4936
|
+
}
|
|
4937
|
+
offset = track.dataEnd;
|
|
4938
|
+
}
|
|
4939
|
+
if (!opusTrack) throw new Error("WebM tracks do not define an Opus audio track.");
|
|
4940
|
+
const audio = findEbmlElement(bytes, opusTrack.dataStart, opusTrack.dataEnd, 225);
|
|
4941
|
+
const sampling = audio ? findEbmlElement(bytes, audio.dataStart, audio.dataEnd, 181) : void 0;
|
|
4942
|
+
const channels = audio ? findEbmlElement(bytes, audio.dataStart, audio.dataEnd, 159) : void 0;
|
|
4943
|
+
const sampleRate = sampling ? new DataView(
|
|
4944
|
+
bytes.buffer,
|
|
4945
|
+
bytes.byteOffset + sampling.dataStart,
|
|
4946
|
+
sampling.dataEnd - sampling.dataStart
|
|
4947
|
+
).getFloat64(0, false) : 0;
|
|
4948
|
+
const channelCount = channels ? bytes[channels.dataEnd - 1] ?? 0 : 0;
|
|
4949
|
+
if (!Number.isFinite(sampleRate) || sampleRate <= 0 || channelCount <= 0)
|
|
4950
|
+
throw new Error("WebM Opus audio track has invalid sampling or channel parameters.");
|
|
4951
|
+
return {
|
|
4952
|
+
format,
|
|
4953
|
+
mimeType: "audio/webm",
|
|
4954
|
+
codec: "opus",
|
|
4955
|
+
sampleRate: Math.round(sampleRate),
|
|
4956
|
+
channels: channelCount,
|
|
4957
|
+
container: "webm",
|
|
4958
|
+
isVbr: true,
|
|
4959
|
+
isCompressed: true
|
|
4960
|
+
};
|
|
4961
|
+
}
|
|
4749
4962
|
function inspectAudioSpecification(buffer, format) {
|
|
4750
4963
|
if (isWavFormat(format) || ascii(new Uint8Array(buffer), 0, "RIFF")) {
|
|
4751
4964
|
const parsed = parseWav(buffer);
|
|
@@ -4770,7 +4983,20 @@ function inspectAudioSpecification(buffer, format) {
|
|
|
4770
4983
|
isCompressed: codec !== "pcm" && codec !== "unknown"
|
|
4771
4984
|
};
|
|
4772
4985
|
}
|
|
4773
|
-
if (isMp3Format(format))
|
|
4986
|
+
if (isMp3Format(format)) {
|
|
4987
|
+
const specification2 = parseMp3Specification(buffer, format);
|
|
4988
|
+
return specification2 ?? formatAudioSpecification(format);
|
|
4989
|
+
}
|
|
4990
|
+
if (isOggFormat(format) || ascii(new Uint8Array(buffer), 0, "OggS")) {
|
|
4991
|
+
const specification2 = parseOggSpecification(buffer, format);
|
|
4992
|
+
validateContainerFormat(specification2, format);
|
|
4993
|
+
return specification2;
|
|
4994
|
+
}
|
|
4995
|
+
if (isWebmFormat(format) || new Uint8Array(buffer)[0] === 26) {
|
|
4996
|
+
const specification2 = parseWebmSpecification(buffer, format);
|
|
4997
|
+
validateContainerFormat(specification2, format);
|
|
4998
|
+
return specification2;
|
|
4999
|
+
}
|
|
4774
5000
|
const specification = formatAudioSpecification(format);
|
|
4775
5001
|
if (specification.container === "raw") validateRawAudioBuffer(buffer, specification);
|
|
4776
5002
|
return specification;
|
|
@@ -4779,7 +5005,20 @@ function validateRawAudioBuffer(buffer, specification) {
|
|
|
4779
5005
|
if (specification.sampleRate <= 0 || specification.channels <= 0 || specification.bitDepth === void 0) {
|
|
4780
5006
|
throw new Error(`RAW audio format "${specification.format}" does not define a complete audio specification.`);
|
|
4781
5007
|
}
|
|
4782
|
-
if (specification.codec === "siren"
|
|
5008
|
+
if (specification.codec === "siren") return;
|
|
5009
|
+
if (specification.codec === "silk") {
|
|
5010
|
+
if (buffer.byteLength <= 9 || !ascii(new Uint8Array(buffer), 0, "#!SILK_V3"))
|
|
5011
|
+
throw new Error("RAW SILK audio must contain a valid #!SILK_V3 payload header.");
|
|
5012
|
+
return;
|
|
5013
|
+
}
|
|
5014
|
+
if (specification.codec === "opus" && buffer.byteLength === 0) throw new Error("RAW Opus audio cannot be empty.");
|
|
5015
|
+
if (specification.codec === "opus") {
|
|
5016
|
+
const packetCode = new Uint8Array(buffer)[0] ?? 0;
|
|
5017
|
+
const frameCountCode = packetCode & 3;
|
|
5018
|
+
if (packetCode >> 3 > 31 || buffer.byteLength < (frameCountCode === 3 ? 2 : 2) || frameCountCode === 3 && ((new Uint8Array(buffer)[1] ?? 0) & 63) === 0)
|
|
5019
|
+
throw new Error("RAW Opus audio has an invalid packet framing header.");
|
|
5020
|
+
return;
|
|
5021
|
+
}
|
|
4783
5022
|
const bytesPerFrame = specification.channels * Math.ceil(specification.bitDepth / 8);
|
|
4784
5023
|
if (bytesPerFrame <= 0 || buffer.byteLength % bytesPerFrame !== 0) {
|
|
4785
5024
|
throw new Error(
|
|
@@ -4787,6 +5026,15 @@ function validateRawAudioBuffer(buffer, specification) {
|
|
|
4787
5026
|
);
|
|
4788
5027
|
}
|
|
4789
5028
|
}
|
|
5029
|
+
function validateContainerFormat(specification, format) {
|
|
5030
|
+
const expected = formatAudioSpecification(format);
|
|
5031
|
+
if (expected.sampleRate > 0 && specification.sampleRate !== expected.sampleRate || expected.channels > 0 && specification.channels !== expected.channels || expected.codec !== "unknown" && specification.codec !== expected.codec) {
|
|
5032
|
+
throw new AudioFormatMismatchError(`Audio container does not match the requested format "${format}".`, [
|
|
5033
|
+
expected,
|
|
5034
|
+
specification
|
|
5035
|
+
]);
|
|
5036
|
+
}
|
|
5037
|
+
}
|
|
4790
5038
|
function validateAudioSpecifications(specs) {
|
|
4791
5039
|
const first = specs[0];
|
|
4792
5040
|
if (!first) return;
|
|
@@ -4868,17 +5116,29 @@ function stripMp3Tags(buffer) {
|
|
|
4868
5116
|
function isMp3Format(format) {
|
|
4869
5117
|
return /(?:mp3|mpeg)/i.test(format);
|
|
4870
5118
|
}
|
|
5119
|
+
function isOggFormat(format) {
|
|
5120
|
+
return /ogg/i.test(format);
|
|
5121
|
+
}
|
|
5122
|
+
function isWebmFormat(format) {
|
|
5123
|
+
return /webm/i.test(format);
|
|
5124
|
+
}
|
|
4871
5125
|
function isWavFormat(format) {
|
|
4872
5126
|
return /(?:wav|wave|riff)/i.test(format);
|
|
4873
5127
|
}
|
|
4874
5128
|
function isRawFormat(format) {
|
|
4875
5129
|
return /^raw(?:-|$)/i.test(format);
|
|
4876
5130
|
}
|
|
4877
|
-
function validateMergedAudioBuffer(merged, format, buffers, inputSpecs, outputMimeType) {
|
|
5131
|
+
function validateMergedAudioBuffer(merged, format, buffers, inputSpecs, outputMimeType, allowExternalContainer) {
|
|
4878
5132
|
if (!(merged instanceof ArrayBuffer) || merged.byteLength === 0) {
|
|
4879
5133
|
throw new MergeError("The custom audio merger returned an empty or invalid audio buffer.");
|
|
4880
5134
|
}
|
|
4881
|
-
|
|
5135
|
+
let specification;
|
|
5136
|
+
try {
|
|
5137
|
+
specification = inspectAudioSpecification(merged, format);
|
|
5138
|
+
} catch (error) {
|
|
5139
|
+
if (!allowExternalContainer) throw error;
|
|
5140
|
+
specification = inputSpecs[0] ?? formatAudioSpecification(format);
|
|
5141
|
+
}
|
|
4882
5142
|
if (!outputMimeType.trim()) throw new MergeError("The custom audio merger output MIME type cannot be empty.");
|
|
4883
5143
|
const firstInput = inputSpecs[0];
|
|
4884
5144
|
if (firstInput && (specification.sampleRate !== firstInput.sampleRate || specification.channels !== firstInput.channels || specification.codec !== firstInput.codec || firstInput.bitDepth !== void 0 && specification.bitDepth !== firstInput.bitDepth)) {
|
|
@@ -4898,6 +5158,25 @@ function validateMergedAudioBuffer(merged, format, buffers, inputSpecs, outputMi
|
|
|
4898
5158
|
}
|
|
4899
5159
|
return specification;
|
|
4900
5160
|
}
|
|
5161
|
+
async function withinDeadline(value, deadline) {
|
|
5162
|
+
if (!deadline) return value;
|
|
5163
|
+
deadline.throwIfExpired();
|
|
5164
|
+
if (!Number.isFinite(deadline.remainingMs)) return value;
|
|
5165
|
+
let timer;
|
|
5166
|
+
try {
|
|
5167
|
+
return await Promise.race([
|
|
5168
|
+
Promise.resolve(value),
|
|
5169
|
+
new Promise((_resolve, reject) => {
|
|
5170
|
+
timer = setTimeout(
|
|
5171
|
+
() => reject(new SynthesisTimeoutError("Speech synthesis exceeded the total job deadline.")),
|
|
5172
|
+
deadline.remainingMs
|
|
5173
|
+
);
|
|
5174
|
+
})
|
|
5175
|
+
]);
|
|
5176
|
+
} finally {
|
|
5177
|
+
if (timer) clearTimeout(timer);
|
|
5178
|
+
}
|
|
5179
|
+
}
|
|
4901
5180
|
function resolveMergeAudioFormat(format) {
|
|
4902
5181
|
if (isWavFormat(format)) return "wav";
|
|
4903
5182
|
if (isMp3Format(format)) return "mp3";
|
|
@@ -4910,6 +5189,7 @@ function canMergeAudioFormat(format) {
|
|
|
4910
5189
|
function mergeAudioBuffers(buffers, options) {
|
|
4911
5190
|
const format = typeof options === "string" ? options : options?.format;
|
|
4912
5191
|
if (!format) throw new UnsupportedMergeFormatError("");
|
|
5192
|
+
if (!canMergeAudioFormat(format)) throw new UnsupportedMergeFormatError(format);
|
|
4913
5193
|
try {
|
|
4914
5194
|
validateAudioSpecifications(buffers.map((buffer) => inspectAudioSpecification(buffer, format)));
|
|
4915
5195
|
if (isWavFormat(format)) return mergeWavBuffers(buffers);
|
|
@@ -5218,10 +5498,23 @@ async function synthesizeWithRetry(ssml, config, retryOptions, onRetry, deadline
|
|
|
5218
5498
|
}
|
|
5219
5499
|
}
|
|
5220
5500
|
async function synthesizeSsml(ssml, config) {
|
|
5221
|
-
const
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5501
|
+
const deadline = new DeadlineController(config.timeouts?.totalJobMs, config.signal);
|
|
5502
|
+
try {
|
|
5503
|
+
deadline.throwIfExpired();
|
|
5504
|
+
const synthesisConfig = {
|
|
5505
|
+
...config,
|
|
5506
|
+
signal: deadline.signal,
|
|
5507
|
+
timeouts: config.timeouts ? { ...config.timeouts, totalJobMs: void 0 } : void 0
|
|
5508
|
+
};
|
|
5509
|
+
const result = config.retryOptions ? await synthesizeWithRetry(ssml, synthesisConfig, config.retryOptions, () => void 0, deadline.deadlineAtMs) : await synthesizeSsmlOnce(ssml, synthesisConfig);
|
|
5510
|
+
deadline.throwIfExpired();
|
|
5511
|
+
return result;
|
|
5512
|
+
} catch (error) {
|
|
5513
|
+
if (deadline.timedOut) throw new SynthesisTimeoutError("Speech synthesis exceeded the total job deadline.");
|
|
5514
|
+
throw error;
|
|
5515
|
+
} finally {
|
|
5516
|
+
deadline.dispose();
|
|
5517
|
+
}
|
|
5225
5518
|
}
|
|
5226
5519
|
function createAbortScope(parent, timeoutMs) {
|
|
5227
5520
|
const controller = new AbortController();
|
|
@@ -5258,7 +5551,12 @@ async function synthesizeSsmlChunks(chunks, config) {
|
|
|
5258
5551
|
const totalChunks = chunks.length;
|
|
5259
5552
|
const inputs = chunks.map((chunk) => typeof chunk === "string" ? { ssml: chunk } : chunk);
|
|
5260
5553
|
const fingerprints = inputs.map(
|
|
5261
|
-
(chunk) => computeChunkFingerprint(chunk.ssml, config.outputFormat ?? DEFAULT_OUTPUT_FORMAT
|
|
5554
|
+
(chunk) => computeChunkFingerprint(chunk.ssml, config.outputFormat ?? DEFAULT_OUTPUT_FORMAT, {
|
|
5555
|
+
region: config.region,
|
|
5556
|
+
endpoint: config.endpoint,
|
|
5557
|
+
customHeaders: config.customHeaders,
|
|
5558
|
+
fingerprintSchemaVersion: config.fingerprintSchemaVersion
|
|
5559
|
+
})
|
|
5262
5560
|
);
|
|
5263
5561
|
const results = new Array(totalChunks);
|
|
5264
5562
|
const cachedChunks = new Map((config.resumeChunks ?? []).map((chunk) => [chunk.chunkIndex, chunk]));
|
|
@@ -5282,6 +5580,7 @@ async function synthesizeSsmlChunks(chunks, config) {
|
|
|
5282
5580
|
const shouldSynthesize = (index) => (!cachedChunks.has(index) || invalidCachedIndices.has(index)) && (requestedIndices === void 0 || requestedIndices.has(index) || invalidCachedIndices.has(index));
|
|
5283
5581
|
const jobStartedAt = Date.now();
|
|
5284
5582
|
const jobDeadlineAt = config.timeouts?.totalJobMs !== void 0 && config.timeouts.totalJobMs > 0 ? jobStartedAt + config.timeouts.totalJobMs : void 0;
|
|
5583
|
+
const deadline = new DeadlineController(config.timeouts?.totalJobMs, config.signal);
|
|
5285
5584
|
const scope = createAbortScope(config.signal, config.timeouts?.totalJobMs);
|
|
5286
5585
|
const report = (event) => config.onProgress?.(event);
|
|
5287
5586
|
for (const [index, input] of inputs.entries()) {
|
|
@@ -5322,7 +5621,7 @@ async function synthesizeSsmlChunks(chunks, config) {
|
|
|
5322
5621
|
input.ssml,
|
|
5323
5622
|
{
|
|
5324
5623
|
...config,
|
|
5325
|
-
signal:
|
|
5624
|
+
signal: deadline.signal,
|
|
5326
5625
|
...input.originalTextRange ? { sourceTextRange: input.originalTextRange } : {},
|
|
5327
5626
|
...input.sourceNodePath ?? config.sourceNodePath ? { sourceNodePath: [...input.sourceNodePath ?? config.sourceNodePath ?? []] } : {},
|
|
5328
5627
|
...input.sourceTextSegments ? { sourceTextSegments: input.sourceTextSegments } : {},
|
|
@@ -5367,7 +5666,7 @@ async function synthesizeSsmlChunks(chunks, config) {
|
|
|
5367
5666
|
status: wasCancelled ? "cancelled" : "failed",
|
|
5368
5667
|
isOriginalFailure: !wasCancelled,
|
|
5369
5668
|
canResume: true,
|
|
5370
|
-
error
|
|
5669
|
+
error: serializeChunkError(error, "synthesis", !wasCancelled)
|
|
5371
5670
|
};
|
|
5372
5671
|
report({
|
|
5373
5672
|
currentChunk: completed,
|
|
@@ -5387,13 +5686,19 @@ async function synthesizeSsmlChunks(chunks, config) {
|
|
|
5387
5686
|
try {
|
|
5388
5687
|
await Promise.all(Array.from({ length: Math.min(concurrency, chunks.length) }, () => worker()));
|
|
5389
5688
|
if (firstError) throw firstError;
|
|
5689
|
+
const missingChunkIndices = Array.from(
|
|
5690
|
+
{ length: totalChunks },
|
|
5691
|
+
(_value, index) => results[index] === void 0 ? index : void 0
|
|
5692
|
+
).filter((index) => index !== void 0);
|
|
5693
|
+
if (missingChunkIndices.length > 0) throw new IncompleteChunkSetError(totalChunks, missingChunkIndices);
|
|
5390
5694
|
const orderedResults = results.filter((result) => result !== void 0);
|
|
5391
5695
|
return await mergeSynthesisResults(orderedResults, {
|
|
5392
5696
|
format: config.outputFormat ?? DEFAULT_OUTPUT_FORMAT,
|
|
5393
|
-
signal:
|
|
5697
|
+
signal: deadline.signal,
|
|
5394
5698
|
customMerger: config.customMerger,
|
|
5395
5699
|
outputMimeType: config.outputMimeType,
|
|
5396
|
-
postMergeValidator: config.postMergeValidator
|
|
5700
|
+
postMergeValidator: config.postMergeValidator,
|
|
5701
|
+
deadline
|
|
5397
5702
|
});
|
|
5398
5703
|
} catch (error) {
|
|
5399
5704
|
if (firstError && config.cancelOnFailure !== false) {
|
|
@@ -5421,6 +5726,7 @@ async function synthesizeSsmlChunks(chunks, config) {
|
|
|
5421
5726
|
throw error;
|
|
5422
5727
|
} finally {
|
|
5423
5728
|
scope.dispose();
|
|
5729
|
+
deadline.dispose();
|
|
5424
5730
|
}
|
|
5425
5731
|
}
|
|
5426
5732
|
function createMergedResult(results, audioData, format, audioSpec, outputMimeType) {
|
|
@@ -5498,19 +5804,33 @@ function mergeSynthesisResults(results, options) {
|
|
|
5498
5804
|
const format = resolvedOptions?.format;
|
|
5499
5805
|
if (!format) throw new UnsupportedMergeFormatError("");
|
|
5500
5806
|
const buffers = results.map((result) => result.audioData);
|
|
5501
|
-
const inputSpecs = results.map((result) =>
|
|
5807
|
+
const inputSpecs = results.map((result) => {
|
|
5808
|
+
if (result.audioSpec) return result.audioSpec;
|
|
5809
|
+
try {
|
|
5810
|
+
return inspectAudioSpecification(result.audioData, format);
|
|
5811
|
+
} catch (error) {
|
|
5812
|
+
if (resolvedOptions.customMerger) return formatAudioSpecification(format);
|
|
5813
|
+
throw error;
|
|
5814
|
+
}
|
|
5815
|
+
});
|
|
5502
5816
|
validateAudioSpecifications(inputSpecs);
|
|
5503
|
-
const
|
|
5817
|
+
const deadline = resolvedOptions.deadline;
|
|
5818
|
+
deadline?.throwIfExpired();
|
|
5819
|
+
const signal = resolvedOptions.signal ?? deadline?.signal ?? new AbortController().signal;
|
|
5504
5820
|
if (signal.aborted) throw new SynthesisCancelledError();
|
|
5505
5821
|
if (resolvedOptions.customMerger) {
|
|
5506
|
-
return
|
|
5507
|
-
()
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5822
|
+
return withinDeadline(
|
|
5823
|
+
Promise.resolve().then(
|
|
5824
|
+
() => resolvedOptions.customMerger?.(buffers, {
|
|
5825
|
+
format,
|
|
5826
|
+
outputMimeType: resolvedOptions.outputMimeType ?? resolveMimeType(format),
|
|
5827
|
+
inputSpecs,
|
|
5828
|
+
signal
|
|
5829
|
+
})
|
|
5830
|
+
),
|
|
5831
|
+
deadline
|
|
5513
5832
|
).then((merged) => {
|
|
5833
|
+
deadline?.throwIfExpired();
|
|
5514
5834
|
if (!merged) throw new MergeError("The custom audio merger returned no audio buffer.");
|
|
5515
5835
|
if (signal.aborted) throw new SynthesisCancelledError();
|
|
5516
5836
|
const mergedSpec = validateMergedAudioBuffer(
|
|
@@ -5518,7 +5838,8 @@ function mergeSynthesisResults(results, options) {
|
|
|
5518
5838
|
format,
|
|
5519
5839
|
buffers,
|
|
5520
5840
|
inputSpecs,
|
|
5521
|
-
resolvedOptions.outputMimeType ?? resolveMimeType(format)
|
|
5841
|
+
resolvedOptions.outputMimeType ?? resolveMimeType(format),
|
|
5842
|
+
true
|
|
5522
5843
|
);
|
|
5523
5844
|
const result = createMergedResult(results, merged, format, mergedSpec, resolvedOptions.outputMimeType);
|
|
5524
5845
|
return Promise.resolve(
|
|
@@ -5529,6 +5850,7 @@ function mergeSynthesisResults(results, options) {
|
|
|
5529
5850
|
signal
|
|
5530
5851
|
})
|
|
5531
5852
|
).then((valid) => {
|
|
5853
|
+
deadline?.throwIfExpired();
|
|
5532
5854
|
if (valid === false) throw new MergeError("The post-merge validator rejected the merged audio.");
|
|
5533
5855
|
return result;
|
|
5534
5856
|
});
|
|
@@ -5539,6 +5861,7 @@ function mergeSynthesisResults(results, options) {
|
|
|
5539
5861
|
});
|
|
5540
5862
|
}
|
|
5541
5863
|
try {
|
|
5864
|
+
deadline?.throwIfExpired();
|
|
5542
5865
|
const result = createMergedResult(
|
|
5543
5866
|
results,
|
|
5544
5867
|
mergeAudioBuffers(buffers, { format }),
|
|
@@ -5554,12 +5877,14 @@ function mergeSynthesisResults(results, options) {
|
|
|
5554
5877
|
signal
|
|
5555
5878
|
});
|
|
5556
5879
|
if (validation instanceof Promise)
|
|
5557
|
-
return validation.then((valid) => {
|
|
5880
|
+
return withinDeadline(validation, deadline).then((valid) => {
|
|
5881
|
+
deadline?.throwIfExpired();
|
|
5558
5882
|
if (valid === false) throw new MergeError("The post-merge validator rejected the merged audio.");
|
|
5559
5883
|
return result;
|
|
5560
5884
|
});
|
|
5561
5885
|
if (validation === false) throw new MergeError("The post-merge validator rejected the merged audio.");
|
|
5562
5886
|
}
|
|
5887
|
+
deadline?.throwIfExpired();
|
|
5563
5888
|
return result;
|
|
5564
5889
|
} catch (error) {
|
|
5565
5890
|
if (error instanceof UnsupportedMergeFormatError || isAudioFormatMismatch(error) || error instanceof MergeError)
|
|
@@ -5704,47 +6029,55 @@ function sharedValidationOptions(options, signal) {
|
|
|
5704
6029
|
};
|
|
5705
6030
|
}
|
|
5706
6031
|
async function synthesizeSsmlSafe(client, ssml, options = {}) {
|
|
5707
|
-
const
|
|
6032
|
+
const deadline = new DeadlineController(options.timeouts?.totalJobMs, options.signal);
|
|
6033
|
+
const validationOptions = sharedValidationOptions(options.validation ?? options, deadline.signal);
|
|
5708
6034
|
const diagnostics = await Promise.resolve(validateAzureSsml2(ssml, validationOptions));
|
|
5709
|
-
if (
|
|
5710
|
-
const error = toSynthesisError(
|
|
6035
|
+
if (deadline.signal.aborted) {
|
|
6036
|
+
const error = toSynthesisError(
|
|
6037
|
+
new Error(deadline.timedOut ? "Speech synthesis timed out." : "Speech synthesis was cancelled.")
|
|
6038
|
+
);
|
|
6039
|
+
deadline.dispose();
|
|
5711
6040
|
return failure(error);
|
|
5712
6041
|
}
|
|
5713
6042
|
const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error");
|
|
5714
6043
|
if (errors.length > 0) {
|
|
6044
|
+
deadline.dispose();
|
|
5715
6045
|
return failure({
|
|
5716
6046
|
kind: "validation-error",
|
|
5717
6047
|
message: "SSML validation failed; the Azure Speech API was not called.",
|
|
5718
6048
|
diagnostics: errors
|
|
5719
6049
|
});
|
|
5720
6050
|
}
|
|
5721
|
-
const jobScope = options.timeouts?.totalJobMs !== void 0 ? createSafeAbortScope(options.signal, options.timeouts.totalJobMs) : void 0;
|
|
5722
6051
|
try {
|
|
5723
6052
|
return {
|
|
5724
6053
|
ok: true,
|
|
5725
6054
|
success: true,
|
|
5726
6055
|
status: "success",
|
|
5727
6056
|
value: await client.synthesizeSsml(ssml, {
|
|
5728
|
-
signal:
|
|
6057
|
+
signal: deadline.signal,
|
|
5729
6058
|
timeoutMs: options.timeouts?.perChunkMs,
|
|
5730
|
-
timeouts: options.timeouts
|
|
6059
|
+
timeouts: options.timeouts ? { ...options.timeouts, totalJobMs: void 0 } : void 0
|
|
5731
6060
|
})
|
|
5732
6061
|
};
|
|
5733
6062
|
} catch (error) {
|
|
5734
|
-
if (
|
|
6063
|
+
if (deadline.timedOut) return failure(toSynthesisError(new Error("Speech synthesis timed out.")));
|
|
5735
6064
|
const synthesisError = toSynthesisError(error);
|
|
5736
6065
|
return failure(synthesisError);
|
|
5737
6066
|
} finally {
|
|
5738
|
-
|
|
6067
|
+
deadline.dispose();
|
|
5739
6068
|
}
|
|
5740
6069
|
}
|
|
5741
6070
|
async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
6071
|
+
const deadline = new DeadlineController(options.timeouts?.totalJobMs, options.signal);
|
|
5742
6072
|
const validationOptions = sharedValidationOptions(
|
|
5743
6073
|
{ ...options.validation ?? options, timeouts: options.timeouts },
|
|
5744
|
-
|
|
6074
|
+
deadline.signal
|
|
5745
6075
|
);
|
|
5746
|
-
if (
|
|
5747
|
-
const error = toSynthesisError(
|
|
6076
|
+
if (deadline.signal.aborted) {
|
|
6077
|
+
const error = toSynthesisError(
|
|
6078
|
+
new Error(deadline.timedOut ? "Speech synthesis timed out." : "Speech synthesis was cancelled.")
|
|
6079
|
+
);
|
|
6080
|
+
deadline.dispose();
|
|
5748
6081
|
return failure(error);
|
|
5749
6082
|
}
|
|
5750
6083
|
const pending = (index, status, error) => {
|
|
@@ -5776,14 +6109,16 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
5776
6109
|
return diagnostics.filter((diagnostic) => diagnostic.severity === "error");
|
|
5777
6110
|
})
|
|
5778
6111
|
);
|
|
5779
|
-
if (
|
|
6112
|
+
if (deadline.signal.aborted) {
|
|
5780
6113
|
const error = toSynthesisError(new Error("Speech synthesis was cancelled."));
|
|
6114
|
+
deadline.dispose();
|
|
5781
6115
|
return failure(error);
|
|
5782
6116
|
}
|
|
5783
6117
|
const chunkDiagnostics = validations.map((diagnostics, chunkIndex) => ({ chunkIndex, diagnostics })).filter((entry) => entry.diagnostics.length > 0);
|
|
5784
6118
|
if (chunkDiagnostics.length > 0) {
|
|
5785
6119
|
const error = new BatchChunkValidationError(chunkDiagnostics);
|
|
5786
6120
|
for (const entry of chunkDiagnostics) pending(entry.chunkIndex, "failed", error);
|
|
6121
|
+
deadline.dispose();
|
|
5787
6122
|
return failure(error);
|
|
5788
6123
|
}
|
|
5789
6124
|
let fallbackJobScope;
|
|
@@ -5796,9 +6131,9 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
5796
6131
|
const value = await client.synthesizeChunks(normalizedChunks, {
|
|
5797
6132
|
onProgress: options.onProgress,
|
|
5798
6133
|
outputFormat: options.outputFormat,
|
|
5799
|
-
signal:
|
|
6134
|
+
signal: deadline.signal,
|
|
5800
6135
|
timeoutMs: options.timeoutMs,
|
|
5801
|
-
timeouts: options.timeouts,
|
|
6136
|
+
timeouts: options.timeouts ? { ...options.timeouts, totalJobMs: void 0 } : void 0,
|
|
5802
6137
|
sourceNodePath: options.sourceNodePath,
|
|
5803
6138
|
concurrency: options.concurrency,
|
|
5804
6139
|
retryOptions: options.retryOptions,
|
|
@@ -5808,12 +6143,19 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
5808
6143
|
customMerger: options.customMerger,
|
|
5809
6144
|
outputMimeType: options.outputMimeType,
|
|
5810
6145
|
postMergeValidator: options.postMergeValidator,
|
|
5811
|
-
resumeValidation: options.resumeValidation
|
|
6146
|
+
resumeValidation: options.resumeValidation,
|
|
6147
|
+
customHeaders: options.customHeaders,
|
|
6148
|
+
fingerprintSchemaVersion: options.fingerprintSchemaVersion
|
|
5812
6149
|
});
|
|
5813
6150
|
return { ok: true, success: true, status: "success", value };
|
|
5814
6151
|
}
|
|
5815
6152
|
const inputs = chunks.map((chunk) => typeof chunk === "string" ? { ssml: chunk } : chunk);
|
|
5816
|
-
const fingerprints = inputs.map(
|
|
6153
|
+
const fingerprints = inputs.map(
|
|
6154
|
+
(chunk) => computeChunkFingerprint(chunk.ssml, options.outputFormat, {
|
|
6155
|
+
customHeaders: options.customHeaders,
|
|
6156
|
+
fingerprintSchemaVersion: options.fingerprintSchemaVersion
|
|
6157
|
+
})
|
|
6158
|
+
);
|
|
5817
6159
|
const results = new Array(chunks.length);
|
|
5818
6160
|
const chunkStates = inputs.map((_chunk, chunkIndex) => ({
|
|
5819
6161
|
chunkIndex,
|
|
@@ -5831,9 +6173,8 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
5831
6173
|
}
|
|
5832
6174
|
const requestedIndices = options.resumeChunkIndices ? new Set(options.resumeChunkIndices.filter((index) => index >= 0 && index < chunks.length)) : void 0;
|
|
5833
6175
|
const shouldSynthesize = (index) => (!cachedChunks.has(index) || invalidCachedIndices.has(index)) && (requestedIndices === void 0 || requestedIndices.has(index) || invalidCachedIndices.has(index));
|
|
5834
|
-
const
|
|
5835
|
-
const
|
|
5836
|
-
const jobScope = chunks.length > 1 || options.timeouts?.totalJobMs !== void 0 ? createSafeAbortScope(options.signal, options.timeouts?.totalJobMs) : void 0;
|
|
6176
|
+
const jobDeadlineAt = deadline.deadlineAtMs;
|
|
6177
|
+
const jobScope = chunks.length > 1 || options.timeouts?.totalJobMs !== void 0 ? createSafeAbortScope(deadline.signal, void 0) : void 0;
|
|
5837
6178
|
fallbackJobScope = jobScope;
|
|
5838
6179
|
const failedIndices = /* @__PURE__ */ new Set();
|
|
5839
6180
|
let firstError;
|
|
@@ -5857,8 +6198,8 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
5857
6198
|
const startedAt = Date.now();
|
|
5858
6199
|
try {
|
|
5859
6200
|
const chunkTimeout = options.timeouts?.chunkWithRetriesMs ?? options.timeouts?.perChunkMs;
|
|
5860
|
-
const chunkScope = chunkTimeout !== void 0 || jobScope ? createSafeAbortScope(jobScope?.signal ??
|
|
5861
|
-
const chunkSignal = chunkScope?.signal ??
|
|
6201
|
+
const chunkScope = chunkTimeout !== void 0 || jobScope ? createSafeAbortScope(jobScope?.signal ?? deadline.signal, chunkTimeout ?? options.timeoutMs) : void 0;
|
|
6202
|
+
const chunkSignal = chunkScope?.signal ?? deadline.signal;
|
|
5862
6203
|
let result;
|
|
5863
6204
|
try {
|
|
5864
6205
|
result = await retryableSynthesis(
|
|
@@ -5885,7 +6226,7 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
5885
6226
|
jobDeadlineAt
|
|
5886
6227
|
);
|
|
5887
6228
|
} catch (error) {
|
|
5888
|
-
if (chunkScope?.timedOut())
|
|
6229
|
+
if (chunkScope?.timedOut() || deadline.timedOut)
|
|
5889
6230
|
throw new Error(`Speech synthesis timed out after ${chunkTimeout ?? options.timeoutMs} ms.`);
|
|
5890
6231
|
throw error;
|
|
5891
6232
|
} finally {
|
|
@@ -5946,15 +6287,15 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
5946
6287
|
durationMs: Date.now() - startedAt
|
|
5947
6288
|
});
|
|
5948
6289
|
} catch (error) {
|
|
5949
|
-
const wasCancelled = firstError !== void 0 || Boolean(jobScope?.signal.aborted && !jobScope?.timedOut());
|
|
5950
|
-
firstError ?? (firstError = error);
|
|
6290
|
+
const wasCancelled = firstError !== void 0 || !deadline.timedOut && Boolean(jobScope?.signal.aborted && !jobScope?.timedOut());
|
|
6291
|
+
firstError ?? (firstError = deadline.timedOut ? new Error("Speech synthesis timed out.") : error);
|
|
5951
6292
|
if (!wasCancelled) failedIndices.add(index);
|
|
5952
6293
|
chunkStates[index] = {
|
|
5953
6294
|
chunkIndex: index,
|
|
5954
6295
|
status: wasCancelled ? "cancelled" : "failed",
|
|
5955
6296
|
isOriginalFailure: !wasCancelled,
|
|
5956
6297
|
canResume: true,
|
|
5957
|
-
error
|
|
6298
|
+
error: serializeChunkError(error, "synthesis", !wasCancelled)
|
|
5958
6299
|
};
|
|
5959
6300
|
options.onProgress?.({
|
|
5960
6301
|
currentChunk: completed,
|
|
@@ -5997,6 +6338,11 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
5997
6338
|
};
|
|
5998
6339
|
throw error;
|
|
5999
6340
|
}
|
|
6341
|
+
const missingChunkIndices = Array.from(
|
|
6342
|
+
{ length: chunks.length },
|
|
6343
|
+
(_value, index) => results[index] === void 0 ? index : void 0
|
|
6344
|
+
).filter((index) => index !== void 0);
|
|
6345
|
+
if (missingChunkIndices.length > 0) throw new IncompleteChunkSetError(chunks.length, missingChunkIndices);
|
|
6000
6346
|
const orderedResults = results.filter((result) => result !== void 0);
|
|
6001
6347
|
return {
|
|
6002
6348
|
ok: true,
|
|
@@ -6004,7 +6350,7 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
6004
6350
|
status: "success",
|
|
6005
6351
|
value: await mergeSynthesisResults(orderedResults, {
|
|
6006
6352
|
format: options.outputFormat ?? "audio-16khz-128kbitrate-mono-mp3",
|
|
6007
|
-
signal: jobScope?.signal ??
|
|
6353
|
+
signal: jobScope?.signal ?? deadline.signal,
|
|
6008
6354
|
customMerger: options.customMerger,
|
|
6009
6355
|
outputMimeType: options.outputMimeType,
|
|
6010
6356
|
postMergeValidator: options.postMergeValidator
|
|
@@ -6015,6 +6361,7 @@ async function synthesizeSsmlChunksSafe(client, chunks, options = {}) {
|
|
|
6015
6361
|
return failure(synthesisError, partialResultFrom(error));
|
|
6016
6362
|
} finally {
|
|
6017
6363
|
fallbackJobScope?.dispose();
|
|
6364
|
+
deadline.dispose();
|
|
6018
6365
|
}
|
|
6019
6366
|
}
|
|
6020
6367
|
function withValidationSignal(options, signal) {
|
|
@@ -6035,7 +6382,16 @@ var AzureTtsClient = class {
|
|
|
6035
6382
|
__privateSet(this, _options, options);
|
|
6036
6383
|
}
|
|
6037
6384
|
async synthesize(ssml) {
|
|
6038
|
-
const {
|
|
6385
|
+
const {
|
|
6386
|
+
region,
|
|
6387
|
+
subscriptionKey,
|
|
6388
|
+
outputFormat,
|
|
6389
|
+
signal,
|
|
6390
|
+
timeoutMs,
|
|
6391
|
+
timeouts,
|
|
6392
|
+
customHeaders,
|
|
6393
|
+
fingerprintSchemaVersion
|
|
6394
|
+
} = __privateGet(this, _options);
|
|
6039
6395
|
const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
6040
6396
|
__privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
|
|
6041
6397
|
const config = {
|
|
@@ -6046,12 +6402,23 @@ var AzureTtsClient = class {
|
|
|
6046
6402
|
signal,
|
|
6047
6403
|
timeoutMs,
|
|
6048
6404
|
timeouts,
|
|
6049
|
-
retryOptions: __privateGet(this, _options).retryOptions
|
|
6405
|
+
retryOptions: __privateGet(this, _options).retryOptions,
|
|
6406
|
+
customHeaders,
|
|
6407
|
+
fingerprintSchemaVersion
|
|
6050
6408
|
};
|
|
6051
6409
|
return synthesizeSpeech(ssml, config);
|
|
6052
6410
|
}
|
|
6053
6411
|
async synthesizeSsml(ssml, options = {}) {
|
|
6054
|
-
const {
|
|
6412
|
+
const {
|
|
6413
|
+
region,
|
|
6414
|
+
subscriptionKey,
|
|
6415
|
+
outputFormat,
|
|
6416
|
+
signal,
|
|
6417
|
+
timeoutMs,
|
|
6418
|
+
timeouts,
|
|
6419
|
+
customHeaders,
|
|
6420
|
+
fingerprintSchemaVersion
|
|
6421
|
+
} = __privateGet(this, _options);
|
|
6055
6422
|
const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
6056
6423
|
__privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
|
|
6057
6424
|
return synthesizeSsml(ssml, {
|
|
@@ -6070,11 +6437,22 @@ var AzureTtsClient = class {
|
|
|
6070
6437
|
customMerger: options.customMerger ?? __privateGet(this, _options).customMerger,
|
|
6071
6438
|
outputMimeType: options.outputMimeType ?? __privateGet(this, _options).outputMimeType,
|
|
6072
6439
|
postMergeValidator: options.postMergeValidator ?? __privateGet(this, _options).postMergeValidator,
|
|
6073
|
-
resumeValidation: options.resumeValidation ?? __privateGet(this, _options).resumeValidation
|
|
6440
|
+
resumeValidation: options.resumeValidation ?? __privateGet(this, _options).resumeValidation,
|
|
6441
|
+
customHeaders: options.customHeaders ?? customHeaders,
|
|
6442
|
+
fingerprintSchemaVersion: options.fingerprintSchemaVersion ?? fingerprintSchemaVersion
|
|
6074
6443
|
});
|
|
6075
6444
|
}
|
|
6076
6445
|
async synthesizeChunks(chunks, options = {}) {
|
|
6077
|
-
const {
|
|
6446
|
+
const {
|
|
6447
|
+
region,
|
|
6448
|
+
subscriptionKey,
|
|
6449
|
+
outputFormat,
|
|
6450
|
+
signal,
|
|
6451
|
+
timeoutMs,
|
|
6452
|
+
timeouts,
|
|
6453
|
+
customHeaders,
|
|
6454
|
+
fingerprintSchemaVersion
|
|
6455
|
+
} = __privateGet(this, _options);
|
|
6078
6456
|
const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
6079
6457
|
return synthesizeSsmlChunks(chunks, {
|
|
6080
6458
|
endpoint,
|
|
@@ -6094,7 +6472,9 @@ var AzureTtsClient = class {
|
|
|
6094
6472
|
customMerger: options.customMerger ?? __privateGet(this, _options).customMerger,
|
|
6095
6473
|
outputMimeType: options.outputMimeType ?? __privateGet(this, _options).outputMimeType,
|
|
6096
6474
|
postMergeValidator: options.postMergeValidator ?? __privateGet(this, _options).postMergeValidator,
|
|
6097
|
-
resumeValidation: options.resumeValidation ?? __privateGet(this, _options).resumeValidation
|
|
6475
|
+
resumeValidation: options.resumeValidation ?? __privateGet(this, _options).resumeValidation,
|
|
6476
|
+
customHeaders: options.customHeaders ?? customHeaders,
|
|
6477
|
+
fingerprintSchemaVersion: options.fingerprintSchemaVersion ?? fingerprintSchemaVersion
|
|
6098
6478
|
});
|
|
6099
6479
|
}
|
|
6100
6480
|
async synthesizeSsmlSafe(ssml, options = {}) {
|
|
@@ -6109,7 +6489,14 @@ var AzureTtsClient = class {
|
|
|
6109
6489
|
timeouts: options.timeouts ?? __privateGet(this, _options).timeouts,
|
|
6110
6490
|
onProgress: options.onProgress ?? __privateGet(this, _options).onProgress,
|
|
6111
6491
|
concurrency: options.concurrency ?? __privateGet(this, _options).concurrency,
|
|
6112
|
-
retryOptions: options.retryOptions ?? __privateGet(this, _options).retryOptions
|
|
6492
|
+
retryOptions: options.retryOptions ?? __privateGet(this, _options).retryOptions,
|
|
6493
|
+
cancelOnFailure: options.cancelOnFailure ?? __privateGet(this, _options).cancelOnFailure,
|
|
6494
|
+
customMerger: options.customMerger ?? __privateGet(this, _options).customMerger,
|
|
6495
|
+
outputMimeType: options.outputMimeType ?? __privateGet(this, _options).outputMimeType,
|
|
6496
|
+
postMergeValidator: options.postMergeValidator ?? __privateGet(this, _options).postMergeValidator,
|
|
6497
|
+
resumeValidation: options.resumeValidation ?? __privateGet(this, _options).resumeValidation,
|
|
6498
|
+
customHeaders: options.customHeaders ?? __privateGet(this, _options).customHeaders,
|
|
6499
|
+
fingerprintSchemaVersion: options.fingerprintSchemaVersion ?? __privateGet(this, _options).fingerprintSchemaVersion
|
|
6113
6500
|
});
|
|
6114
6501
|
}
|
|
6115
6502
|
async synthesizeSsmlChunksSafe(chunks, options = {}) {
|
|
@@ -6215,6 +6602,8 @@ async function fetchAzureVoiceCatalog(options) {
|
|
|
6215
6602
|
BatchChunkValidationError,
|
|
6216
6603
|
ChunkValidationError,
|
|
6217
6604
|
DEFAULT_OUTPUT_FORMAT,
|
|
6605
|
+
DeadlineController,
|
|
6606
|
+
IncompleteChunkSetError,
|
|
6218
6607
|
MergeError,
|
|
6219
6608
|
SynthesisCancelledError,
|
|
6220
6609
|
SynthesisTimeoutError,
|
|
@@ -6242,6 +6631,7 @@ async function fetchAzureVoiceCatalog(options) {
|
|
|
6242
6631
|
parseSsml,
|
|
6243
6632
|
resolveMergeAudioFormat,
|
|
6244
6633
|
resolveMimeType,
|
|
6634
|
+
serializeChunkError,
|
|
6245
6635
|
splitSsmlDocument,
|
|
6246
6636
|
synthesizeSpeech,
|
|
6247
6637
|
synthesizeSsml,
|