@speech-sdk/core 0.31.0 → 0.33.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 +39 -2
- package/dist/apply-speed.d.ts +1 -0
- package/dist/apply-speed.d.ts.map +1 -1
- package/dist/apply-speed.js +7 -5
- package/dist/apply-speed.js.map +1 -1
- package/dist/audio-decode.js +3 -2
- package/dist/audio-decode.js.map +1 -1
- package/dist/audio-output.d.ts +1 -0
- package/dist/audio-output.d.ts.map +1 -1
- package/dist/audio-output.js +1 -1
- package/dist/audio-output.js.map +1 -1
- package/dist/conversation/attribute-timestamps.d.ts +1 -21
- package/dist/conversation/attribute-timestamps.d.ts.map +1 -1
- package/dist/conversation/attribute-timestamps.js +26 -264
- package/dist/conversation/attribute-timestamps.js.map +1 -1
- package/dist/conversation/dispatch.d.ts +4 -2
- package/dist/conversation/dispatch.d.ts.map +1 -1
- package/dist/conversation/dispatch.js +47 -18
- package/dist/conversation/dispatch.js.map +1 -1
- package/dist/conversation/errors.d.ts +9 -0
- package/dist/conversation/errors.d.ts.map +1 -1
- package/dist/conversation/errors.js +18 -0
- package/dist/conversation/errors.js.map +1 -1
- package/dist/conversation/pcm-concat.d.ts +13 -0
- package/dist/conversation/pcm-concat.d.ts.map +1 -1
- package/dist/conversation/pcm-concat.js +22 -4
- package/dist/conversation/pcm-concat.js.map +1 -1
- package/dist/conversation/split-turns.d.ts +31 -0
- package/dist/conversation/split-turns.d.ts.map +1 -0
- package/dist/conversation/split-turns.js +158 -0
- package/dist/conversation/split-turns.js.map +1 -0
- package/dist/conversation/stitch.d.ts +1 -3
- package/dist/conversation/stitch.d.ts.map +1 -1
- package/dist/conversation/stitch.js +10 -15
- package/dist/conversation/stitch.js.map +1 -1
- package/dist/conversation/types.d.ts +5 -0
- package/dist/conversation/types.d.ts.map +1 -1
- package/dist/conversation/validate.d.ts.map +1 -1
- package/dist/conversation/validate.js +3 -0
- package/dist/conversation/validate.js.map +1 -1
- package/dist/generate-conversation.d.ts +7 -3
- package/dist/generate-conversation.d.ts.map +1 -1
- package/dist/generate-conversation.js +154 -105
- package/dist/generate-conversation.js.map +1 -1
- package/dist/generate-speech.d.ts.map +1 -1
- package/dist/generate-speech.js +55 -14
- package/dist/generate-speech.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/metadata.d.ts +9 -0
- package/dist/metadata.d.ts.map +1 -1
- package/dist/providers/google/index.d.ts +3 -0
- package/dist/providers/google/index.d.ts.map +1 -1
- package/dist/providers/google/index.js +203 -64
- package/dist/providers/google/index.js.map +1 -1
- package/dist/sentence-boundaries.d.ts.map +1 -1
- package/dist/sentence-boundaries.js +2 -2
- package/dist/sentence-boundaries.js.map +1 -1
- package/dist/speech-provider.d.ts +1 -0
- package/dist/speech-provider.d.ts.map +1 -1
- package/dist/speech-provider.js.map +1 -1
- package/dist/speech-result.d.ts +20 -0
- package/dist/speech-result.d.ts.map +1 -1
- package/dist/speech-result.js.map +1 -1
- package/dist/text-chunker.d.ts +2 -0
- package/dist/text-chunker.d.ts.map +1 -1
- package/dist/text-chunker.js +68 -1
- package/dist/text-chunker.js.map +1 -1
- package/dist/timestamp-finalization.d.ts +1 -0
- package/dist/timestamp-finalization.d.ts.map +1 -1
- package/dist/timestamp-finalization.js +5 -5
- package/dist/timestamp-finalization.js.map +1 -1
- package/dist/types.d.ts +3 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/conversation/proportional-fill.d.ts +0 -10
- package/dist/conversation/proportional-fill.d.ts.map +0 -1
- package/dist/conversation/proportional-fill.js +0 -64
- package/dist/conversation/proportional-fill.js.map +0 -1
- package/dist/conversation/silence-detection.d.ts +0 -14
- package/dist/conversation/silence-detection.d.ts.map +0 -1
- package/dist/conversation/silence-detection.js +0 -52
- package/dist/conversation/silence-detection.js.map +0 -1
|
@@ -27,6 +27,11 @@ export interface GenerateConversationOptions<V extends Voice = Voice, M extends
|
|
|
27
27
|
readonly pronunciations?: PronunciationsInput;
|
|
28
28
|
readonly providerOptions?: Record<string, unknown>;
|
|
29
29
|
readonly speed?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Also return `turns`: one audio clip per input turn, cut at the silence after each turn's last word. Requires
|
|
32
|
+
* `timestamps: true`. Throws `TurnSplitError` when turn boundaries can't be trusted.
|
|
33
|
+
*/
|
|
34
|
+
readonly splitTurns?: boolean;
|
|
30
35
|
readonly timestampProvider?: TimestampProvider;
|
|
31
36
|
readonly timestamps?: boolean;
|
|
32
37
|
readonly turns: readonly ConversationTurn<V>[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/conversation/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAElE,MAAM,WAAW,gBAAgB,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK;IACvD,mDAAmD;IACnD,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;IAC3C,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEnD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,0DAA0D;IAC1D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;CACnB;AAED,MAAM,WAAW,2BAA2B,CAC1C,CAAC,SAAS,KAAK,GAAG,KAAK,EACvB,CAAC,SAAS,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,GAAG,SAAS,GAC3C,MAAM,GACN,aAAa,CAAC,CAAC,CAAC,GAChB,SAAS;IAEb,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC;IACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,iEAAiE;IACjE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACnB,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,cAAc,CAAC,EAAE,mBAAmB,CAAC;IAC9C,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEnD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IAC/C,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,SAAS,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC;IAE/C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/conversation/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAElE,MAAM,WAAW,gBAAgB,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK;IACvD,mDAAmD;IACnD,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;IAC3C,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEnD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,0DAA0D;IAC1D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;CACnB;AAED,MAAM,WAAW,2BAA2B,CAC1C,CAAC,SAAS,KAAK,GAAG,KAAK,EACvB,CAAC,SAAS,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,GAAG,SAAS,GAC3C,MAAM,GACN,aAAa,CAAC,CAAC,CAAC,GAChB,SAAS;IAEb,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC;IACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,iEAAiE;IACjE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACnB,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,cAAc,CAAC,EAAE,mBAAmB,CAAC;IAC9C,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEnD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IAC/C,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,SAAS,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC;IAE/C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/conversation/validate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAC;AAEhF,wBAAgB,aAAa,IAAI,CAAC,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAAC,KAAK,MAAM,CAE5E;AAED,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,2BAA2B,GACnC,IAAI,
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/conversation/validate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAC;AAEhF,wBAAgB,aAAa,IAAI,CAAC,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAAC,KAAK,MAAM,CAE5E;AAED,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,2BAA2B,GACnC,IAAI,CAiCN"}
|
|
@@ -6,6 +6,9 @@ export function validateConversationInput(options) {
|
|
|
6
6
|
if (options.turns.length === 0) {
|
|
7
7
|
throw new ConversationInputError("generateConversation requires at least one turn.");
|
|
8
8
|
}
|
|
9
|
+
if (options.splitTurns && options.timestamps !== true) {
|
|
10
|
+
throw new ConversationInputError("splitTurns requires timestamps: true — turn boundaries come from word timestamps.");
|
|
11
|
+
}
|
|
9
12
|
// Model placement must be all-or-nothing — partial mix hides which model actually ran where.
|
|
10
13
|
const hasTopLevel = options.model != null;
|
|
11
14
|
for (let i = 0; i < options.turns.length; i++) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/conversation/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAGrD,MAAM,UAAU,aAAa;IAC3B,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,EAAE,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,yBAAyB,CACvC,OAAoC;IAEpC,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,sBAAsB,CAC9B,kDAAkD,CACnD,CAAC;IACJ,CAAC;IAED,6FAA6F;IAC7F,MAAM,WAAW,GAAG,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC;IAE1C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC9C,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC9B,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAClC,MAAM,IAAI,sBAAsB,CAAC,SAAS,CAAC,2BAA2B,CAAC,CAAC;QAC1E,CAAC;QACD,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC;QACxC,IAAI,WAAW,IAAI,YAAY,EAAE,CAAC;YAChC,MAAM,IAAI,sBAAsB,CAC9B,SAAS,CAAC,kIAAkI,CAC7I,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,CAAC,WAAW,IAAI,YAAY,CAAC,EAAE,CAAC;YACnC,MAAM,IAAI,sBAAsB,CAC9B,SAAS,CAAC,2HAA2H,CACtI,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/conversation/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAGrD,MAAM,UAAU,aAAa;IAC3B,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,EAAE,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,yBAAyB,CACvC,OAAoC;IAEpC,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,sBAAsB,CAC9B,kDAAkD,CACnD,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;QACtD,MAAM,IAAI,sBAAsB,CAC9B,mFAAmF,CACpF,CAAC;IACJ,CAAC;IAED,6FAA6F;IAC7F,MAAM,WAAW,GAAG,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC;IAE1C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC9C,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC9B,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAClC,MAAM,IAAI,sBAAsB,CAAC,SAAS,CAAC,2BAA2B,CAAC,CAAC;QAC1E,CAAC;QACD,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC;QACxC,IAAI,WAAW,IAAI,YAAY,EAAE,CAAC;YAChC,MAAM,IAAI,sBAAsB,CAC9B,SAAS,CAAC,kIAAkI,CAC7I,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,CAAC,WAAW,IAAI,YAAY,CAAC,EAAE,CAAC;YACnC,MAAM,IAAI,sBAAsB,CAC9B,SAAS,CAAC,2HAA2H,CACtI,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import type { GenerateConversationOptions } from "./conversation/types.js";
|
|
2
2
|
import { type ResolvedModel, type Voice } from "./speech-provider.js";
|
|
3
|
-
import type { ConversationResult, ConversationResultWithTimestamps } from "./speech-result.js";
|
|
4
|
-
export { ConversationInputError, DialogueConstraintError, StitchUnsupportedError, } from "./conversation/errors.js";
|
|
3
|
+
import type { ConversationResult, ConversationResultWithTimestamps, ConversationResultWithTurns } from "./speech-result.js";
|
|
4
|
+
export { ConversationInputError, DialogueConstraintError, StitchUnsupportedError, TurnSplitError, type TurnSplitFailureReason, } from "./conversation/errors.js";
|
|
5
5
|
export type { ConversationTurn, GenerateConversationOptions, } from "./conversation/types.js";
|
|
6
|
-
export type { ConversationMetadata, ConversationResult, ConversationResultWithTimestamps, } from "./speech-result.js";
|
|
6
|
+
export type { ConversationMetadata, ConversationPathKind, ConversationResult, ConversationResultWithTimestamps, ConversationResultWithTurns, ConversationStitchReason, ConversationTurnAudio, } from "./speech-result.js";
|
|
7
|
+
export declare function generateConversation<V extends Voice = Voice, M extends string | ResolvedModel<V> | undefined = string | ResolvedModel<V> | undefined>(options: GenerateConversationOptions<V, M> & {
|
|
8
|
+
timestamps: true;
|
|
9
|
+
splitTurns: true;
|
|
10
|
+
}): Promise<ConversationResultWithTurns>;
|
|
7
11
|
export declare function generateConversation<V extends Voice = Voice, M extends string | ResolvedModel<V> | undefined = string | ResolvedModel<V> | undefined>(options: GenerateConversationOptions<V, M> & {
|
|
8
12
|
timestamps: true;
|
|
9
13
|
}): Promise<ConversationResultWithTimestamps>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-conversation.d.ts","sourceRoot":"","sources":["../src/generate-conversation.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"generate-conversation.d.ts","sourceRoot":"","sources":["../src/generate-conversation.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAEV,2BAA2B,EAC5B,MAAM,yBAAyB,CAAC;AAwBjC,OAAO,EAGL,KAAK,aAAa,EAElB,KAAK,KAAK,EACX,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAEV,kBAAkB,EAClB,gCAAgC,EAChC,2BAA2B,EAC5B,MAAM,oBAAoB,CAAC;AAU5B,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,sBAAsB,EACtB,cAAc,EACd,KAAK,sBAAsB,GAC5B,MAAM,0BAA0B,CAAC;AAClC,YAAY,EACV,gBAAgB,EAChB,2BAA2B,GAC5B,MAAM,yBAAyB,CAAC;AACjC,YAAY,EACV,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,EAClB,gCAAgC,EAChC,2BAA2B,EAC3B,wBAAwB,EACxB,qBAAqB,GACtB,MAAM,oBAAoB,CAAC;AAkC5B,wBAAgB,oBAAoB,CAClC,CAAC,SAAS,KAAK,GAAG,KAAK,EACvB,CAAC,SAAS,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,GAAG,SAAS,GAC3C,MAAM,GACN,aAAa,CAAC,CAAC,CAAC,GAChB,SAAS,EAEb,OAAO,EAAE,2BAA2B,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG;IAC3C,UAAU,EAAE,IAAI,CAAC;IACjB,UAAU,EAAE,IAAI,CAAC;CAClB,GACA,OAAO,CAAC,2BAA2B,CAAC,CAAC;AACxC,wBAAgB,oBAAoB,CAClC,CAAC,SAAS,KAAK,GAAG,KAAK,EACvB,CAAC,SAAS,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,GAAG,SAAS,GAC3C,MAAM,GACN,aAAa,CAAC,CAAC,CAAC,GAChB,SAAS,EAEb,OAAO,EAAE,2BAA2B,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG;IAAE,UAAU,EAAE,IAAI,CAAA;CAAE,GAChE,OAAO,CAAC,gCAAgC,CAAC,CAAC;AAC7C,wBAAgB,oBAAoB,CAClC,CAAC,SAAS,KAAK,GAAG,KAAK,EACvB,CAAC,SAAS,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,GAAG,SAAS,GAC3C,MAAM,GACN,aAAa,CAAC,CAAC,CAAC,GAChB,SAAS,EACb,OAAO,EAAE,2BAA2B,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import pRetry from "p-retry";
|
|
2
|
-
import { applySpeedToAudio, isSpeedActive, scaleTimestamps, validateSpeed, } from "./apply-speed.js";
|
|
2
|
+
import { applySpeedToAudio, isSpeedActive, outputAfterSpeed, scaleTimestamps, validateSpeed, } from "./apply-speed.js";
|
|
3
3
|
import { computeAudioDuration } from "./audio-duration.js";
|
|
4
4
|
import { applyOptionalOutputConversion, sampleRateHintFrom, validateOutput, } from "./audio-output.js";
|
|
5
5
|
import { mapWithConcurrency, resolveMaxConcurrency } from "./concurrency.js";
|
|
6
6
|
import { chooseConversationPath } from "./conversation/dispatch.js";
|
|
7
|
+
import { TurnSplitError } from "./conversation/errors.js";
|
|
7
8
|
import { validateConversationInput } from "./conversation/validate.js";
|
|
8
9
|
import { deriveTimestampsViaSTT } from "./derive-timestamps.js";
|
|
9
10
|
import { NoSpeechGeneratedError, OutputConversionUnsupportedError, TimestampProviderRequiredError, TimestampValidationError, } from "./errors.js";
|
|
@@ -21,7 +22,7 @@ import { preprocessSpeechText } from "./text-preprocessing.js";
|
|
|
21
22
|
import { deriveTimestampsViaProvider } from "./timestamp-alignment.js";
|
|
22
23
|
import { finalizeTimestamps } from "./timestamp-finalization.js";
|
|
23
24
|
// biome-ignore lint/performance/noBarrelFile: public entry point — re-export error classes so callers get fn + types + errors from one import
|
|
24
|
-
export { ConversationInputError, DialogueConstraintError, StitchUnsupportedError, } from "./conversation/errors.js";
|
|
25
|
+
export { ConversationInputError, DialogueConstraintError, StitchUnsupportedError, TurnSplitError, } from "./conversation/errors.js";
|
|
25
26
|
const DEFAULT_GAP_MS = 300;
|
|
26
27
|
const DEFAULT_MAX_RETRIES = 2;
|
|
27
28
|
function requireValidTimestamps(args) {
|
|
@@ -84,12 +85,18 @@ export async function generateConversation(options) {
|
|
|
84
85
|
for (const [index, resolved] of resolvedPerTurn.entries()) {
|
|
85
86
|
validateInstructionSupport(resolved, combineInstructions(options.instructions, options.turns[index].instructions));
|
|
86
87
|
}
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
88
|
+
const needsMaxInputCharsStitch = needsConversationStitchForMaxInputChars({
|
|
89
|
+
resolvedPerTurn,
|
|
90
|
+
turns: options.turns,
|
|
91
|
+
userMaxInputChars: options.maxInputChars,
|
|
92
|
+
});
|
|
93
|
+
let forceStitch;
|
|
94
|
+
if (hasPerTurnSpeed) {
|
|
95
|
+
forceStitch = "per-turn-speed";
|
|
96
|
+
}
|
|
97
|
+
else if (needsMaxInputCharsStitch) {
|
|
98
|
+
forceStitch = "max-input-chars";
|
|
99
|
+
}
|
|
93
100
|
const path = chooseConversationPath({
|
|
94
101
|
forceStitch,
|
|
95
102
|
resolvedPerTurn,
|
|
@@ -97,14 +104,13 @@ export async function generateConversation(options) {
|
|
|
97
104
|
output: options.output,
|
|
98
105
|
});
|
|
99
106
|
if (path.kind === "native") {
|
|
100
|
-
return await
|
|
101
|
-
|
|
107
|
+
return await finishConversation({
|
|
108
|
+
run: await runNativeDispatch({
|
|
102
109
|
options,
|
|
103
110
|
resolved: path.resolved,
|
|
104
111
|
blocks: path.blocks,
|
|
105
112
|
}),
|
|
106
|
-
|
|
107
|
-
output: options.output,
|
|
113
|
+
options,
|
|
108
114
|
});
|
|
109
115
|
}
|
|
110
116
|
// Lazy-load so native-only callers don't bundle pcm-concat / mediabunny.
|
|
@@ -119,7 +125,6 @@ export async function generateConversation(options) {
|
|
|
119
125
|
maxConcurrency: resolveMaxConcurrency(options.maxConcurrency),
|
|
120
126
|
maxInputChars: options.maxInputChars,
|
|
121
127
|
maxRetries: options.maxRetries ?? DEFAULT_MAX_RETRIES,
|
|
122
|
-
output: options.output,
|
|
123
128
|
volumeDbfs: options.volumeDbfs,
|
|
124
129
|
abortSignal: options.abortSignal,
|
|
125
130
|
headers: options.headers,
|
|
@@ -127,8 +132,6 @@ export async function generateConversation(options) {
|
|
|
127
132
|
timestamps: options.timestamps ?? false,
|
|
128
133
|
timestampProvider: options.timestampProvider,
|
|
129
134
|
pronunciations: options.pronunciations,
|
|
130
|
-
// Defer output conversion to applySpeedToConversationResult to avoid encoding twice.
|
|
131
|
-
deferOutputConversion: isSpeedActive(options.speed),
|
|
132
135
|
});
|
|
133
136
|
if (stitched.audio.length === 0) {
|
|
134
137
|
throw new NoSpeechGeneratedError(`${describeConversationModels(resolvedPerTurn)}: stitched conversation audio is empty.`, { reason: "provider_empty_response" });
|
|
@@ -140,11 +143,16 @@ export async function generateConversation(options) {
|
|
|
140
143
|
audioDurationMs: stitched.metadata.audioDurationMs,
|
|
141
144
|
}),
|
|
142
145
|
perTurn: stitched.metadataPerTurn,
|
|
146
|
+
path: "stitch",
|
|
147
|
+
stitchReason: path.stitchReason,
|
|
143
148
|
};
|
|
144
149
|
let fallbackWarning;
|
|
145
150
|
if (path.reason === "fallback-from-native") {
|
|
146
151
|
fallbackWarning = `native dialogue unavailable because per-turn providerOptions are set; rendered via stitch (${options.turns.length} API calls instead of 1)`;
|
|
147
152
|
}
|
|
153
|
+
else if (path.reason === "fallback-from-native-custom-voice") {
|
|
154
|
+
fallbackWarning = `native dialogue requires prebuilt voices; custom voices were rendered via stitch (${options.turns.length} generateSpeech calls) instead`;
|
|
155
|
+
}
|
|
148
156
|
else if (path.reason === "fallback-from-native-oversized") {
|
|
149
157
|
fallbackWarning = `native dialogue exceeds the provider's per-call limit and couldn't be split into voice-valid blocks; rendered via stitch (${options.turns.length} API calls instead of 1)`;
|
|
150
158
|
}
|
|
@@ -157,20 +165,67 @@ export async function generateConversation(options) {
|
|
|
157
165
|
const combinedWarnings = fallbackWarning
|
|
158
166
|
? [fallbackWarning, ...stitched.warnings]
|
|
159
167
|
: stitched.warnings;
|
|
160
|
-
return await
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
168
|
+
return await finishConversation({
|
|
169
|
+
run: {
|
|
170
|
+
result: {
|
|
171
|
+
audio: new DefaultGeneratedAudioFile({
|
|
172
|
+
data: stitched.audio,
|
|
173
|
+
mediaType: stitched.mediaType,
|
|
174
|
+
}),
|
|
175
|
+
metadata,
|
|
176
|
+
providerMetadata: { turns: stitched.providerMetadataPerTurn },
|
|
177
|
+
warnings: combinedWarnings.length > 0 ? [...combinedWarnings] : undefined,
|
|
178
|
+
timestamps: stitched.timestamps,
|
|
179
|
+
},
|
|
180
|
+
knownCutsSec: stitched.turnCutsSec,
|
|
170
181
|
},
|
|
182
|
+
options,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
// Runners return decodable audio; the requested output format is encoded exactly once, here.
|
|
186
|
+
async function finishConversation(args) {
|
|
187
|
+
const { options, run } = args;
|
|
188
|
+
const speed = isSpeedActive(options.speed) ? options.speed : 1;
|
|
189
|
+
const output = speed === 1 ? options.output : outputAfterSpeed(options.output);
|
|
190
|
+
const result = await applySpeedToConversationResult({
|
|
191
|
+
result: run.result,
|
|
171
192
|
speed: options.speed,
|
|
172
|
-
output: options.output,
|
|
173
193
|
});
|
|
194
|
+
if (!options.splitTurns) {
|
|
195
|
+
return await encodeConversationAudio(result, output);
|
|
196
|
+
}
|
|
197
|
+
const { splitConversationTurns } = await import("./conversation/split-turns.js");
|
|
198
|
+
const split = await splitConversationTurns({
|
|
199
|
+
audio: result.audio.uint8Array,
|
|
200
|
+
knownCutsSec: run.knownCutsSec?.map((sec) => sec == null ? sec : sec / speed),
|
|
201
|
+
mediaType: result.audio.mediaType,
|
|
202
|
+
output,
|
|
203
|
+
timestamps: result.timestamps ?? [],
|
|
204
|
+
turnCount: options.turns.length,
|
|
205
|
+
});
|
|
206
|
+
const encoded = await encodeConversationAudio(result, output, {
|
|
207
|
+
audio: new Uint8Array(split.pcm.buffer, split.pcm.byteOffset, split.pcm.byteLength),
|
|
208
|
+
mediaType: `audio/pcm;rate=${split.sampleRate}`,
|
|
209
|
+
});
|
|
210
|
+
return { ...encoded, turns: split.turns };
|
|
211
|
+
}
|
|
212
|
+
async function encodeConversationAudio(result, output,
|
|
213
|
+
// Same audio already decoded, when the caller has it, to skip a second decode.
|
|
214
|
+
source = {
|
|
215
|
+
audio: result.audio.uint8Array,
|
|
216
|
+
mediaType: result.audio.mediaType,
|
|
217
|
+
}) {
|
|
218
|
+
if (!output) {
|
|
219
|
+
return result;
|
|
220
|
+
}
|
|
221
|
+
const converted = await applyOptionalOutputConversion({ ...source, output });
|
|
222
|
+
return {
|
|
223
|
+
...result,
|
|
224
|
+
audio: new DefaultGeneratedAudioFile({
|
|
225
|
+
data: converted.audio,
|
|
226
|
+
mediaType: converted.mediaType,
|
|
227
|
+
}),
|
|
228
|
+
};
|
|
174
229
|
}
|
|
175
230
|
function needsConversationStitchForMaxInputChars(args) {
|
|
176
231
|
let forceStitch = false;
|
|
@@ -220,6 +275,27 @@ function finalizeConversationTurnTimestamps(args) {
|
|
|
220
275
|
});
|
|
221
276
|
return projected;
|
|
222
277
|
}
|
|
278
|
+
// Force decodable PCM/WAV via getStitchOptions for normalization; if unavailable, emit the provider's mixed audio and warn.
|
|
279
|
+
function resolveNativeStitchOptions(options, resolved) {
|
|
280
|
+
const stitchOpts = resolved.provider.getStitchOptions?.(resolved.modelId, {
|
|
281
|
+
sampleRate: sampleRateHintFrom(options.output),
|
|
282
|
+
});
|
|
283
|
+
if (stitchOpts) {
|
|
284
|
+
return { stitchOpts, warnings: [] };
|
|
285
|
+
}
|
|
286
|
+
if (options.splitTurns) {
|
|
287
|
+
throw new TurnSplitError({ reason: "undecodable_audio" });
|
|
288
|
+
}
|
|
289
|
+
if (options.output) {
|
|
290
|
+
throw new OutputConversionUnsupportedError(`${resolved.provider.id}/${resolved.modelId}`);
|
|
291
|
+
}
|
|
292
|
+
return {
|
|
293
|
+
stitchOpts,
|
|
294
|
+
warnings: [
|
|
295
|
+
`${resolved.provider.id}/${resolved.modelId}: native dialogue path returns the provider's mixed audio without volume normalization (no decodable PCM/WAV mode).`,
|
|
296
|
+
],
|
|
297
|
+
};
|
|
298
|
+
}
|
|
223
299
|
async function runNative(args) {
|
|
224
300
|
const { options, resolved, maxRetries } = args;
|
|
225
301
|
const start = performance.now();
|
|
@@ -227,17 +303,7 @@ async function runNative(args) {
|
|
|
227
303
|
throw new Error(`generateConversation: ${resolved.provider.id}/${resolved.modelId} dispatched to native but generateDialogue missing`);
|
|
228
304
|
}
|
|
229
305
|
const generateDialogue = resolved.provider.generateDialogue.bind(resolved.provider);
|
|
230
|
-
|
|
231
|
-
const stitchOpts = resolved.provider.getStitchOptions?.(resolved.modelId, {
|
|
232
|
-
sampleRate: sampleRateHintFrom(options.output),
|
|
233
|
-
});
|
|
234
|
-
const warnings = [];
|
|
235
|
-
if (!stitchOpts) {
|
|
236
|
-
warnings.push(`${resolved.provider.id}/${resolved.modelId}: native dialogue path returns the provider's mixed audio without volume normalization (no decodable PCM/WAV mode).`);
|
|
237
|
-
}
|
|
238
|
-
if (options.output && !stitchOpts) {
|
|
239
|
-
throw new OutputConversionUnsupportedError(`${resolved.provider.id}/${resolved.modelId}`);
|
|
240
|
-
}
|
|
306
|
+
const { stitchOpts, warnings } = resolveNativeStitchOptions(options, resolved);
|
|
241
307
|
// Stitch options must win — caller-supplied response_format would break the decoder.
|
|
242
308
|
const dialogueProviderOptions = stitchOpts
|
|
243
309
|
? { ...options.providerOptions, ...stitchOpts.providerOptions }
|
|
@@ -289,7 +355,7 @@ async function runNative(args) {
|
|
|
289
355
|
});
|
|
290
356
|
const computedDuration = await computeAudioDuration(audio.uint8Array, outputMediaType);
|
|
291
357
|
const audioDurationMs = computedDuration ?? result.audioDurationMs;
|
|
292
|
-
const
|
|
358
|
+
const rawTimestamps = await resolveNativeDialogueTimestamps({
|
|
293
359
|
requestTimestamps,
|
|
294
360
|
nativeTimestamps: result.timestamps,
|
|
295
361
|
hasNativeTimestamps: hasNativeDialogueTimestamps,
|
|
@@ -311,42 +377,30 @@ async function runNative(args) {
|
|
|
311
377
|
timestamps: projection.timestamps,
|
|
312
378
|
turnTexts: substitutedTurns.map((turn) => turn.originalText),
|
|
313
379
|
});
|
|
314
|
-
// Defer output conversion to applySpeedToConversationResult when top-level speed
|
|
315
|
-
// is active — otherwise we'd encode here and re-encode in the stretch step.
|
|
316
|
-
const deferOutput = isSpeedActive(options.speed);
|
|
317
|
-
const converted = await applyOptionalOutputConversion({
|
|
318
|
-
audio: audio.uint8Array,
|
|
319
|
-
mediaType: outputMediaType,
|
|
320
|
-
output: deferOutput ? undefined : options.output,
|
|
321
|
-
});
|
|
322
|
-
const finalAudio = options.output && !deferOutput
|
|
323
|
-
? new DefaultGeneratedAudioFile({
|
|
324
|
-
data: converted.audio,
|
|
325
|
-
mediaType: converted.mediaType,
|
|
326
|
-
})
|
|
327
|
-
: audio;
|
|
328
380
|
const inputChars = options.turns.reduce((n, t) => n + t.text.length, 0);
|
|
329
381
|
const metadata = {
|
|
330
382
|
latencyMs,
|
|
331
383
|
inputChars,
|
|
332
384
|
...(audioDurationMs != null && { audioDurationMs }),
|
|
385
|
+
path: "native",
|
|
333
386
|
};
|
|
334
387
|
const preprocessingWarnings = substitutedTurns.flatMap((turn) => turn.warnings);
|
|
335
388
|
const mergedWarningList = [
|
|
336
389
|
...warnings,
|
|
337
390
|
...preprocessingWarnings,
|
|
338
|
-
...attributionWarnings,
|
|
339
391
|
...(projection.estimatedBoundaries
|
|
340
392
|
? [PRONUNCIATION_TIMESTAMP_ESTIMATE_WARNING]
|
|
341
393
|
: []),
|
|
342
394
|
];
|
|
343
395
|
const mergedWarnings = mergedWarningList.length > 0 ? mergedWarningList : undefined;
|
|
344
396
|
return {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
397
|
+
result: {
|
|
398
|
+
audio,
|
|
399
|
+
metadata,
|
|
400
|
+
providerMetadata: result.providerMetadata,
|
|
401
|
+
warnings: mergedWarnings,
|
|
402
|
+
timestamps,
|
|
403
|
+
},
|
|
350
404
|
};
|
|
351
405
|
}
|
|
352
406
|
async function runNativeDispatch(args) {
|
|
@@ -429,7 +483,7 @@ async function runNativeSplit(args) {
|
|
|
429
483
|
mediaType: stitchOpts.mediaType,
|
|
430
484
|
}).uint8Array;
|
|
431
485
|
const segment = await decodeAudioToPcm16(blockAudio, stitchOpts.mediaType);
|
|
432
|
-
const
|
|
486
|
+
const timestamps = await resolveNativeDialogueTimestamps({
|
|
433
487
|
requestTimestamps,
|
|
434
488
|
nativeTimestamps: result.timestamps,
|
|
435
489
|
hasNativeTimestamps,
|
|
@@ -441,20 +495,29 @@ async function runNativeSplit(args) {
|
|
|
441
495
|
audioDurationMs: (segment.pcm.length / segment.sampleRate) * 1000,
|
|
442
496
|
substitutedTurnTexts: blockTurns.map((t) => t.canonicalText),
|
|
443
497
|
timestampProvider: options.timestampProvider,
|
|
444
|
-
pcmSegment: segment,
|
|
445
498
|
});
|
|
446
499
|
return {
|
|
447
500
|
segment,
|
|
448
501
|
timestamps,
|
|
449
|
-
warnings,
|
|
450
502
|
providerMetadata: result.providerMetadata,
|
|
451
503
|
};
|
|
452
504
|
}, { signal: options.abortSignal });
|
|
453
|
-
const {
|
|
505
|
+
const { concatPcmToWavWithRanges, dbfsToInt16Rms, gapMidpointsSec, normalizeRms, stitchTargetRate, } = await import("./conversation/pcm-concat.js");
|
|
454
506
|
const segments = perBlock.map((p) => p.segment);
|
|
455
507
|
const leveled = normalizeRms(segments, options.volumeDbfs == null ? undefined : dbfsToInt16Rms(options.volumeDbfs));
|
|
456
508
|
const targetSampleRate = stitchTargetRate(leveled);
|
|
457
|
-
const wav = await
|
|
509
|
+
const { wav, ranges } = await concatPcmToWavWithRanges(leveled, {
|
|
510
|
+
gapMs,
|
|
511
|
+
targetSampleRate,
|
|
512
|
+
});
|
|
513
|
+
// Block joins are the only boundaries the SDK cut itself; boundaries inside a block stay undefined.
|
|
514
|
+
const knownCutsSec = [];
|
|
515
|
+
for (const [b, cutSec] of gapMidpointsSec(ranges, targetSampleRate).entries()) {
|
|
516
|
+
const lastTurn = blocks[b].at(-1);
|
|
517
|
+
if (lastTurn != null) {
|
|
518
|
+
knownCutsSec[lastTurn] = cutSec;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
458
521
|
const timestampProjection = requestTimestamps
|
|
459
522
|
? composeBlockTimestamps({
|
|
460
523
|
perBlock,
|
|
@@ -465,15 +528,9 @@ async function runNativeSplit(args) {
|
|
|
465
528
|
})
|
|
466
529
|
: { estimatedBoundaries: false, timestamps: undefined };
|
|
467
530
|
const timestamps = timestampProjection.timestamps;
|
|
468
|
-
const deferOutput = isSpeedActive(options.speed);
|
|
469
|
-
const converted = await applyOptionalOutputConversion({
|
|
470
|
-
audio: wav,
|
|
471
|
-
mediaType: "audio/wav",
|
|
472
|
-
output: deferOutput ? undefined : options.output,
|
|
473
|
-
});
|
|
474
531
|
const finalAudio = new DefaultGeneratedAudioFile({
|
|
475
|
-
data:
|
|
476
|
-
mediaType:
|
|
532
|
+
data: wav,
|
|
533
|
+
mediaType: "audio/wav",
|
|
477
534
|
});
|
|
478
535
|
// Derive duration from the PCM sample counts (resampled to the stitch rate plus gaps)
|
|
479
536
|
// instead of re-decoding the merged audio — matches the stitch path.
|
|
@@ -486,20 +543,23 @@ async function runNativeSplit(args) {
|
|
|
486
543
|
latencyMs: Math.round(performance.now() - start),
|
|
487
544
|
inputChars,
|
|
488
545
|
...(audioDurationMs != null && { audioDurationMs }),
|
|
546
|
+
path: "native-split",
|
|
489
547
|
};
|
|
490
548
|
const warnings = [
|
|
491
549
|
...substitutedTurns.flatMap((turn) => turn.warnings),
|
|
492
|
-
...perBlock.flatMap((p) => p.warnings),
|
|
493
550
|
...(timestampProjection.estimatedBoundaries
|
|
494
551
|
? [PRONUNCIATION_TIMESTAMP_ESTIMATE_WARNING]
|
|
495
552
|
: []),
|
|
496
553
|
];
|
|
497
554
|
return {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
555
|
+
result: {
|
|
556
|
+
audio: finalAudio,
|
|
557
|
+
metadata,
|
|
558
|
+
providerMetadata: { blocks: perBlock.map((p) => p.providerMetadata) },
|
|
559
|
+
warnings: warnings.length > 0 ? [...warnings] : undefined,
|
|
560
|
+
timestamps,
|
|
561
|
+
},
|
|
562
|
+
knownCutsSec,
|
|
503
563
|
};
|
|
504
564
|
}
|
|
505
565
|
function composeBlockTimestamps(args) {
|
|
@@ -538,7 +598,7 @@ function composeBlockTimestamps(args) {
|
|
|
538
598
|
}
|
|
539
599
|
async function resolveNativeDialogueTimestamps(args) {
|
|
540
600
|
if (!args.requestTimestamps) {
|
|
541
|
-
return
|
|
601
|
+
return;
|
|
542
602
|
}
|
|
543
603
|
// Either use native flat timestamps, or derive via STT fallback.
|
|
544
604
|
let flatTimestamps;
|
|
@@ -582,36 +642,24 @@ async function resolveNativeDialogueTimestamps(args) {
|
|
|
582
642
|
text: args.substitutedTurnTexts.join(" "),
|
|
583
643
|
timestamps: flatTimestamps,
|
|
584
644
|
});
|
|
585
|
-
const { detectSilenceGaps } = await import("./conversation/silence-detection.js");
|
|
586
645
|
const { attributeTimestamps } = await import("./conversation/attribute-timestamps.js");
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
const segment = args.pcmSegment ??
|
|
590
|
-
(await (await import("./audio-decode.js")).decodeAudioToPcm16(args.audio, args.mediaType));
|
|
591
|
-
const gaps = detectSilenceGaps(segment.pcm, {
|
|
592
|
-
sampleRate: segment.sampleRate,
|
|
593
|
-
minDurationMs: 150,
|
|
594
|
-
});
|
|
595
|
-
silenceGaps = gaps;
|
|
596
|
-
}
|
|
597
|
-
catch {
|
|
598
|
-
// Decoder couldn't read the audio (e.g., compressed format we can't decode locally).
|
|
599
|
-
// Tier 1 will be skipped; dispatcher falls through to Tier 2/3.
|
|
600
|
-
}
|
|
601
|
-
const result = attributeTimestamps({
|
|
646
|
+
// Validated words cover the joined turn text exactly, so an exact partition always exists.
|
|
647
|
+
const attributed = attributeTimestamps({
|
|
602
648
|
timestamps: flatTimestamps,
|
|
603
649
|
turnTexts: args.substitutedTurnTexts,
|
|
604
|
-
silenceGaps,
|
|
605
650
|
});
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
651
|
+
if (!attributed) {
|
|
652
|
+
throw new TimestampValidationError({
|
|
653
|
+
reason: "transcript_mismatch",
|
|
609
654
|
source: args.ttsModel,
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
return finalizeConversationTurnTimestamps({
|
|
658
|
+
audioDurationMs: args.audioDurationMs,
|
|
659
|
+
source: args.ttsModel,
|
|
660
|
+
timestamps: attributed,
|
|
661
|
+
turnTexts: args.substitutedTurnTexts,
|
|
662
|
+
});
|
|
615
663
|
}
|
|
616
664
|
async function buildNativeAudio(args) {
|
|
617
665
|
let audioBytes = args.result.audio;
|
|
@@ -663,7 +711,7 @@ function buildSubstitutedTurns(turns, resolved, ruleMap) {
|
|
|
663
711
|
});
|
|
664
712
|
}
|
|
665
713
|
async function applySpeedToConversationResult(args) {
|
|
666
|
-
const { result, speed
|
|
714
|
+
const { result, speed } = args;
|
|
667
715
|
if (!isSpeedActive(speed)) {
|
|
668
716
|
return result;
|
|
669
717
|
}
|
|
@@ -671,7 +719,8 @@ async function applySpeedToConversationResult(args) {
|
|
|
671
719
|
audio: result.audio.uint8Array,
|
|
672
720
|
mediaType: result.audio.mediaType,
|
|
673
721
|
speed,
|
|
674
|
-
|
|
722
|
+
// finishConversation encodes the requested format once, after any split.
|
|
723
|
+
output: { format: "wav" },
|
|
675
724
|
});
|
|
676
725
|
const newAudio = new DefaultGeneratedAudioFile({
|
|
677
726
|
data: stretched.audio,
|