@ssml-builder-js/azure-tts-client 2.16.0 → 2.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/dist/index.d.mts +223 -127
- package/dist/index.d.ts +223 -127
- package/dist/index.js +552 -98
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +549 -98
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/client.ts +29 -4
- package/src/errors.ts +44 -2
- package/src/index.ts +19 -2
- package/src/outputFormats.ts +3 -0
- package/src/safe.ts +280 -42
- package/src/synthesis.ts +380 -57
- package/src/types.ts +85 -1
- package/src/voiceCatalog.ts +4 -0
- package/test/v217-pipeline.test.ts +125 -0
- package/test/v218-pipeline.test.ts +91 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ssml-builder-js/azure-tts-client",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.18.0",
|
|
4
4
|
"description": "Azure Text-to-Speech client using the Microsoft Speech SDK",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"typescript": "^6.0.3"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@ssml-builder-js/ssml-core": "^2.
|
|
38
|
+
"@ssml-builder-js/ssml-core": "^2.18.0",
|
|
39
39
|
"microsoft-cognitiveservices-speech-sdk": "1.51.0"
|
|
40
40
|
}
|
|
41
41
|
}
|
package/src/client.ts
CHANGED
|
@@ -19,16 +19,25 @@ export class AzureTtsClient {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
async synthesize(ssml: string): Promise<ArrayBuffer> {
|
|
22
|
-
const { region, subscriptionKey, outputFormat, signal, timeoutMs } = this.#options;
|
|
22
|
+
const { region, subscriptionKey, outputFormat, signal, timeoutMs, timeouts } = this.#options;
|
|
23
23
|
const endpoint = this.#options.endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
24
24
|
this.#options.logger?.debug?.("Using Azure TTS endpoint:", endpoint);
|
|
25
25
|
|
|
26
|
-
const config = {
|
|
26
|
+
const config = {
|
|
27
|
+
endpoint,
|
|
28
|
+
region,
|
|
29
|
+
subscriptionKey,
|
|
30
|
+
outputFormat,
|
|
31
|
+
signal,
|
|
32
|
+
timeoutMs,
|
|
33
|
+
timeouts,
|
|
34
|
+
retryOptions: this.#options.retryOptions,
|
|
35
|
+
};
|
|
27
36
|
return synthesizeSpeech(ssml, config);
|
|
28
37
|
}
|
|
29
38
|
|
|
30
39
|
async synthesizeSsml(ssml: string, options: Partial<TtsConfig> = {}): Promise<SsmlSynthesisResult> {
|
|
31
|
-
const { region, subscriptionKey, outputFormat, signal, timeoutMs } = this.#options;
|
|
40
|
+
const { region, subscriptionKey, outputFormat, signal, timeoutMs, timeouts } = this.#options;
|
|
32
41
|
const endpoint = this.#options.endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
33
42
|
this.#options.logger?.debug?.("Using Azure TTS endpoint:", endpoint);
|
|
34
43
|
|
|
@@ -39,9 +48,16 @@ export class AzureTtsClient {
|
|
|
39
48
|
outputFormat: options.outputFormat ?? outputFormat,
|
|
40
49
|
signal: options.signal ?? signal,
|
|
41
50
|
timeoutMs: options.timeoutMs ?? timeoutMs,
|
|
51
|
+
timeouts: options.timeouts ?? timeouts,
|
|
42
52
|
sourceNodePath: options.sourceNodePath,
|
|
43
53
|
sourceTextSegments: options.sourceTextSegments,
|
|
44
54
|
sourceMarkers: options.sourceMarkers,
|
|
55
|
+
retryOptions: options.retryOptions ?? this.#options.retryOptions,
|
|
56
|
+
cancelOnFailure: options.cancelOnFailure ?? this.#options.cancelOnFailure,
|
|
57
|
+
customMerger: options.customMerger ?? this.#options.customMerger,
|
|
58
|
+
outputMimeType: options.outputMimeType ?? this.#options.outputMimeType,
|
|
59
|
+
postMergeValidator: options.postMergeValidator ?? this.#options.postMergeValidator,
|
|
60
|
+
resumeValidation: options.resumeValidation ?? this.#options.resumeValidation,
|
|
45
61
|
});
|
|
46
62
|
}
|
|
47
63
|
|
|
@@ -49,7 +65,7 @@ export class AzureTtsClient {
|
|
|
49
65
|
chunks: readonly (SsmlSynthesisChunk | string)[],
|
|
50
66
|
options: SynthesizeChunksOptions = {},
|
|
51
67
|
): Promise<SsmlSynthesisResult> {
|
|
52
|
-
const { region, subscriptionKey, outputFormat, signal, timeoutMs } = this.#options;
|
|
68
|
+
const { region, subscriptionKey, outputFormat, signal, timeoutMs, timeouts } = this.#options;
|
|
53
69
|
const endpoint = this.#options.endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
54
70
|
return synthesizeSsmlChunks(chunks, {
|
|
55
71
|
endpoint,
|
|
@@ -58,10 +74,18 @@ export class AzureTtsClient {
|
|
|
58
74
|
outputFormat: options.outputFormat ?? outputFormat,
|
|
59
75
|
signal: options.signal ?? signal,
|
|
60
76
|
timeoutMs: options.timeoutMs ?? timeoutMs,
|
|
77
|
+
timeouts: options.timeouts ?? timeouts,
|
|
61
78
|
sourceNodePath: options.sourceNodePath,
|
|
62
79
|
onProgress: options.onProgress ?? this.#options.onProgress,
|
|
63
80
|
concurrency: options.concurrency ?? this.#options.concurrency,
|
|
64
81
|
retryOptions: options.retryOptions ?? this.#options.retryOptions,
|
|
82
|
+
cancelOnFailure: options.cancelOnFailure ?? this.#options.cancelOnFailure,
|
|
83
|
+
resumeChunks: options.resumeChunks,
|
|
84
|
+
resumeChunkIndices: options.resumeChunkIndices,
|
|
85
|
+
customMerger: options.customMerger ?? this.#options.customMerger,
|
|
86
|
+
outputMimeType: options.outputMimeType ?? this.#options.outputMimeType,
|
|
87
|
+
postMergeValidator: options.postMergeValidator ?? this.#options.postMergeValidator,
|
|
88
|
+
resumeValidation: options.resumeValidation ?? this.#options.resumeValidation,
|
|
65
89
|
});
|
|
66
90
|
}
|
|
67
91
|
|
|
@@ -78,6 +102,7 @@ export class AzureTtsClient {
|
|
|
78
102
|
outputFormat: options.outputFormat ?? this.#options.outputFormat,
|
|
79
103
|
signal: options.signal ?? this.#options.signal,
|
|
80
104
|
timeoutMs: options.timeoutMs ?? this.#options.timeoutMs,
|
|
105
|
+
timeouts: options.timeouts ?? this.#options.timeouts,
|
|
81
106
|
onProgress: options.onProgress ?? this.#options.onProgress,
|
|
82
107
|
concurrency: options.concurrency ?? this.#options.concurrency,
|
|
83
108
|
retryOptions: options.retryOptions ?? this.#options.retryOptions,
|
package/src/errors.ts
CHANGED
|
@@ -13,15 +13,57 @@ export class AzureTtsError extends Error {
|
|
|
13
13
|
readonly statusText: string;
|
|
14
14
|
readonly responseBody: string;
|
|
15
15
|
readonly requestId: string | null;
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
readonly retryAfterMs?: number;
|
|
17
|
+
|
|
18
|
+
constructor(
|
|
19
|
+
status: number,
|
|
20
|
+
statusText: string,
|
|
21
|
+
responseBody: string,
|
|
22
|
+
requestId: string | null,
|
|
23
|
+
responseHeaders?: Headers | Readonly<Record<string, string>>,
|
|
24
|
+
) {
|
|
18
25
|
super(`Azure TTS request failed: ${status} ${statusText}`);
|
|
19
26
|
this.name = "AzureTtsError";
|
|
20
27
|
this.status = status;
|
|
21
28
|
this.statusText = statusText;
|
|
22
29
|
this.responseBody = responseBody;
|
|
23
30
|
this.requestId = requestId;
|
|
31
|
+
const value =
|
|
32
|
+
responseHeaders instanceof Headers
|
|
33
|
+
? responseHeaders.get("retry-after")
|
|
34
|
+
: (responseHeaders?.["retry-after"] ?? responseHeaders?.["Retry-After"]);
|
|
35
|
+
const seconds = value ? Number(value.trim()) : NaN;
|
|
36
|
+
const date = value ? Date.parse(value) : NaN;
|
|
37
|
+
if (Number.isFinite(seconds) && seconds >= 0) this.retryAfterMs = seconds * 1000;
|
|
38
|
+
else if (Number.isFinite(date)) this.retryAfterMs = Math.max(0, date - Date.now());
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Reads Retry-After from an error-like value, returning milliseconds when present. */
|
|
43
|
+
export function getRetryAfterDelayMs(error: unknown): number | undefined {
|
|
44
|
+
if (error instanceof AzureTtsError && error.retryAfterMs !== undefined) return error.retryAfterMs;
|
|
45
|
+
if (!error || typeof error !== "object") return undefined;
|
|
46
|
+
const candidate = error as { retryAfterMs?: unknown; headers?: unknown; response?: unknown };
|
|
47
|
+
if (typeof candidate.retryAfterMs === "number" && candidate.retryAfterMs >= 0) return candidate.retryAfterMs;
|
|
48
|
+
const headers = candidate.headers ?? (candidate.response as { headers?: unknown } | undefined)?.headers;
|
|
49
|
+
if (headers instanceof Headers) {
|
|
50
|
+
const value = headers.get("retry-after");
|
|
51
|
+
if (!value) return undefined;
|
|
52
|
+
const seconds = Number(value.trim());
|
|
53
|
+
if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1000;
|
|
54
|
+
const date = Date.parse(value);
|
|
55
|
+
return Number.isFinite(date) ? Math.max(0, date - Date.now()) : undefined;
|
|
56
|
+
}
|
|
57
|
+
if (headers && typeof headers === "object") {
|
|
58
|
+
const value =
|
|
59
|
+
(headers as Record<string, unknown>)["retry-after"] ?? (headers as Record<string, unknown>)["Retry-After"];
|
|
60
|
+
if (typeof value !== "string") return undefined;
|
|
61
|
+
const seconds = Number(value.trim());
|
|
62
|
+
if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1000;
|
|
63
|
+
const date = Date.parse(value);
|
|
64
|
+
return Number.isFinite(date) ? Math.max(0, date - Date.now()) : undefined;
|
|
24
65
|
}
|
|
66
|
+
return undefined;
|
|
25
67
|
}
|
|
26
68
|
|
|
27
69
|
export class AzureTtsSdkError extends AzureTtsError {
|
package/src/index.ts
CHANGED
|
@@ -17,6 +17,16 @@ export type {
|
|
|
17
17
|
SynthesizeChunksOptions,
|
|
18
18
|
SynthesisChunkStatus,
|
|
19
19
|
RetryOptions,
|
|
20
|
+
SynthesisTimeouts,
|
|
21
|
+
SynthesizedChunk,
|
|
22
|
+
PartialChunkSynthesisResult,
|
|
23
|
+
PartialSynthesisResult,
|
|
24
|
+
ChunkExecutionState,
|
|
25
|
+
ChunkExecutionStatus,
|
|
26
|
+
ResumeValidationMode,
|
|
27
|
+
CustomMergerContext,
|
|
28
|
+
CustomAudioMerger,
|
|
29
|
+
PostMergeValidator,
|
|
20
30
|
TtsConfig,
|
|
21
31
|
} from "./types.ts";
|
|
22
32
|
export {
|
|
@@ -27,6 +37,7 @@ export {
|
|
|
27
37
|
SynthesisCancelledError,
|
|
28
38
|
SynthesisTimeoutError,
|
|
29
39
|
UnsupportedMergeFormatError,
|
|
40
|
+
getRetryAfterDelayMs,
|
|
30
41
|
} from "./errors.ts";
|
|
31
42
|
export type { AzureTtsSynthesisError, SynthesisErrorKind } from "./errors.ts";
|
|
32
43
|
export { AzureTtsClient } from "./client.ts";
|
|
@@ -39,9 +50,9 @@ export {
|
|
|
39
50
|
inspectAudioSpecification,
|
|
40
51
|
resolveMergeAudioFormat,
|
|
41
52
|
synthesizeSsmlChunks,
|
|
53
|
+
computeChunkFingerprint,
|
|
42
54
|
} from "./synthesis.ts";
|
|
43
55
|
export type {
|
|
44
|
-
CustomMergerContext,
|
|
45
56
|
InputAudioSpecs,
|
|
46
57
|
MergeAudioFormat,
|
|
47
58
|
MergeAudioOptions,
|
|
@@ -49,7 +60,12 @@ export type {
|
|
|
49
60
|
} from "./synthesis.ts";
|
|
50
61
|
export { DEFAULT_OUTPUT_FORMAT, resolveMimeType } from "./outputFormats.ts";
|
|
51
62
|
export type { AzureTtsOutputFormat } from "./outputFormats.ts";
|
|
52
|
-
export {
|
|
63
|
+
export {
|
|
64
|
+
BatchChunkValidationError,
|
|
65
|
+
ChunkValidationError,
|
|
66
|
+
synthesizeSsmlChunksSafe,
|
|
67
|
+
synthesizeSsmlSafe,
|
|
68
|
+
} from "./safe.ts";
|
|
53
69
|
export type {
|
|
54
70
|
AzureApiErrorResult,
|
|
55
71
|
Result,
|
|
@@ -62,6 +78,7 @@ export type {
|
|
|
62
78
|
SsmlSynthesisChunksSafeResult,
|
|
63
79
|
SynthesizeSsmlChunksSafeOptions,
|
|
64
80
|
ValidationErrorResult,
|
|
81
|
+
ChunkDiagnostics,
|
|
65
82
|
} from "./safe.ts";
|
|
66
83
|
export { fetchAzureVoiceCatalog } from "./voiceCatalog.ts";
|
|
67
84
|
export type {
|
package/src/outputFormats.ts
CHANGED
|
@@ -49,6 +49,9 @@ export type AzureTtsOutputFormat = keyof typeof OUTPUT_FORMATS;
|
|
|
49
49
|
export function resolveMimeType(outputFormat: string): string {
|
|
50
50
|
if (/(?:wav|wave|riff)/i.test(outputFormat)) return "audio/wav";
|
|
51
51
|
if (/(?:mp3|mpeg)/i.test(outputFormat)) return "audio/mpeg";
|
|
52
|
+
if (/mulaw|mu-law/i.test(outputFormat)) return "audio/basic";
|
|
53
|
+
if (/alaw|a-law/i.test(outputFormat)) return "audio/alaw";
|
|
54
|
+
if (/siren/i.test(outputFormat)) return "audio/siren";
|
|
52
55
|
if (/ogg/i.test(outputFormat)) return "audio/ogg";
|
|
53
56
|
if (/webm/i.test(outputFormat)) return "audio/webm";
|
|
54
57
|
if (/raw/i.test(outputFormat)) return "audio/L16";
|