@scenar/cli 0.0.2
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/LICENSE +190 -0
- package/bin/scenar.d.ts +3 -0
- package/bin/scenar.d.ts.map +1 -0
- package/bin/scenar.js +4 -0
- package/bin/scenar.js.map +1 -0
- package/package.json +43 -0
- package/src/__tests__/edge-tts-provider.test.ts +86 -0
- package/src/__tests__/load-yaml.test.ts +78 -0
- package/src/__tests__/narrate-command.test.ts +119 -0
- package/src/__tests__/openai-provider.test.ts +63 -0
- package/src/__tests__/resolve-provider.test.ts +59 -0
- package/src/__tests__/scenario-validator.test.ts +353 -0
- package/src/__tests__/validate-command.test.ts +80 -0
- package/src/commands/narrate.d.ts +15 -0
- package/src/commands/narrate.d.ts.map +1 -0
- package/src/commands/narrate.js +75 -0
- package/src/commands/narrate.js.map +1 -0
- package/src/commands/narrate.ts +111 -0
- package/src/commands/validate.d.ts +8 -0
- package/src/commands/validate.d.ts.map +1 -0
- package/src/commands/validate.js +33 -0
- package/src/commands/validate.js.map +1 -0
- package/src/commands/validate.ts +44 -0
- package/src/index.d.ts +4 -0
- package/src/index.d.ts.map +1 -0
- package/src/index.js +18 -0
- package/src/index.js.map +1 -0
- package/src/index.ts +22 -0
- package/src/tts/echogarden.d.ts +18 -0
- package/src/tts/echogarden.d.ts.map +1 -0
- package/src/tts/echogarden.js +54 -0
- package/src/tts/echogarden.js.map +1 -0
- package/src/tts/echogarden.ts +61 -0
- package/src/tts/edge-tts.d.ts +19 -0
- package/src/tts/edge-tts.d.ts.map +1 -0
- package/src/tts/edge-tts.js +63 -0
- package/src/tts/edge-tts.js.map +1 -0
- package/src/tts/edge-tts.ts +73 -0
- package/src/tts/openai.d.ts +7 -0
- package/src/tts/openai.d.ts.map +1 -0
- package/src/tts/openai.js +54 -0
- package/src/tts/openai.js.map +1 -0
- package/src/tts/openai.ts +68 -0
- package/src/tts/resolve-provider.d.ts +11 -0
- package/src/tts/resolve-provider.d.ts.map +1 -0
- package/src/tts/resolve-provider.js +47 -0
- package/src/tts/resolve-provider.js.map +1 -0
- package/src/tts/resolve-provider.ts +61 -0
- package/src/tts/types.d.ts +41 -0
- package/src/tts/types.d.ts.map +1 -0
- package/src/tts/types.js +2 -0
- package/src/tts/types.js.map +1 -0
- package/src/tts/types.ts +45 -0
- package/src/util/load-yaml.d.ts +14 -0
- package/src/util/load-yaml.d.ts.map +1 -0
- package/src/util/load-yaml.js +55 -0
- package/src/util/load-yaml.js.map +1 -0
- package/src/util/load-yaml.ts +58 -0
- package/src/validate/scenario-validator.d.ts +17 -0
- package/src/validate/scenario-validator.d.ts.map +1 -0
- package/src/validate/scenario-validator.js +174 -0
- package/src/validate/scenario-validator.js.map +1 -0
- package/src/validate/scenario-validator.ts +227 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../../src/commands/validate.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAMrE,MAAM,UAAU,uBAAuB,CAAC,OAAgB;IACtD,OAAO;SACJ,OAAO,CAAC,UAAU,CAAC;SACnB,WAAW,CAAC,yDAAyD,CAAC;SACtE,QAAQ,CAAC,QAAQ,EAAE,4BAA4B,CAAC;SAChD,MAAM,CAAC,QAAQ,EAAE,kCAAkC,CAAC;SACpD,MAAM,CAAC,KAAK,EAAE,IAAY,EAAE,OAAwB,EAAE,EAAE;QACvD,MAAM,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;AACP,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,IAAY,EACZ,OAAwB;IAExB,MAAM,QAAQ,GAAG,MAAM,gBAAgB,CAAC,IAAI,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAE1C,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QACjB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QAC7D,IAAI,CAAC,MAAM,CAAC,KAAK;YAAE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACxC,OAAO;IACT,CAAC;IAED,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,uCAAuC,IAAI,IAAI,CAAC,CAAC;QACtE,OAAO;IACT,CAAC;IAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iCAAiC,MAAM,CAAC,MAAM,CAAC,MAAM,cAAc,IAAI,MAAM,CAAC,CAAC;IACpG,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,sBAAsB,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;IAC9E,CAAC;IACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3B,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { loadScenarioYaml } from "../util/load-yaml.js";
|
|
3
|
+
import { validateScenario } from "../validate/scenario-validator.js";
|
|
4
|
+
|
|
5
|
+
interface ValidateOptions {
|
|
6
|
+
json?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function registerValidateCommand(program: Command): void {
|
|
10
|
+
program
|
|
11
|
+
.command("validate")
|
|
12
|
+
.description("Validate a scenario YAML file against the proto schema.")
|
|
13
|
+
.argument("<file>", "path to scenario YAML file")
|
|
14
|
+
.option("--json", "output validation result as JSON")
|
|
15
|
+
.action(async (file: string, options: ValidateOptions) => {
|
|
16
|
+
await runValidate(file, options);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function runValidate(
|
|
21
|
+
file: string,
|
|
22
|
+
options: ValidateOptions,
|
|
23
|
+
): Promise<void> {
|
|
24
|
+
const scenario = await loadScenarioYaml(file);
|
|
25
|
+
const result = validateScenario(scenario);
|
|
26
|
+
|
|
27
|
+
if (options.json) {
|
|
28
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
29
|
+
if (!result.valid) process.exitCode = 1;
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (result.valid) {
|
|
34
|
+
process.stdout.write(`\x1b[32m✓\x1b[0m Scenario is valid: ${file}\n`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
process.stderr.write(`\x1b[31m✗\x1b[0m Scenario has ${result.errors.length} error(s): ${file}\n\n`);
|
|
39
|
+
for (const error of result.errors) {
|
|
40
|
+
process.stderr.write(` \x1b[31m•\x1b[0m ${error.path}: ${error.reason}\n`);
|
|
41
|
+
}
|
|
42
|
+
process.stderr.write("\n");
|
|
43
|
+
process.exitCode = 1;
|
|
44
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,wBAAgB,aAAa,IAAI,OAAO,CAYvC;AAED,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAGxC"}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { registerValidateCommand } from "./commands/validate.js";
|
|
3
|
+
import { registerNarrateCommand } from "./commands/narrate.js";
|
|
4
|
+
export function createProgram() {
|
|
5
|
+
const program = new Command();
|
|
6
|
+
program
|
|
7
|
+
.name("scenar")
|
|
8
|
+
.description("Scenar CLI — validate scenario YAML and generate narration audio.")
|
|
9
|
+
.version("0.0.1");
|
|
10
|
+
registerValidateCommand(program);
|
|
11
|
+
registerNarrateCommand(program);
|
|
12
|
+
return program;
|
|
13
|
+
}
|
|
14
|
+
export function run(argv) {
|
|
15
|
+
const program = createProgram();
|
|
16
|
+
program.parse(argv);
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
package/src/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAE/D,MAAM,UAAU,aAAa;IAC3B,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;IAE9B,OAAO;SACJ,IAAI,CAAC,QAAQ,CAAC;SACd,WAAW,CAAC,mEAAmE,CAAC;SAChF,OAAO,CAAC,OAAO,CAAC,CAAC;IAEpB,uBAAuB,CAAC,OAAO,CAAC,CAAC;IACjC,sBAAsB,CAAC,OAAO,CAAC,CAAC;IAEhC,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,GAAG,CAAC,IAAc;IAChC,MAAM,OAAO,GAAG,aAAa,EAAE,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AACtB,CAAC"}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { registerValidateCommand } from "./commands/validate.js";
|
|
3
|
+
import { registerNarrateCommand } from "./commands/narrate.js";
|
|
4
|
+
|
|
5
|
+
export function createProgram(): Command {
|
|
6
|
+
const program = new Command();
|
|
7
|
+
|
|
8
|
+
program
|
|
9
|
+
.name("scenar")
|
|
10
|
+
.description("Scenar CLI — validate scenario YAML and generate narration audio.")
|
|
11
|
+
.version("0.0.1");
|
|
12
|
+
|
|
13
|
+
registerValidateCommand(program);
|
|
14
|
+
registerNarrateCommand(program);
|
|
15
|
+
|
|
16
|
+
return program;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function run(argv: string[]): void {
|
|
20
|
+
const program = createProgram();
|
|
21
|
+
program.parse(argv);
|
|
22
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { TtsProvider } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Checks whether the `echogarden` package is available at runtime.
|
|
4
|
+
* Returns `true` if it can be imported, `false` otherwise.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isEchogardenAvailable(): Promise<boolean>;
|
|
7
|
+
/**
|
|
8
|
+
* TTS provider backed by Echogarden — a TypeScript-native, offline
|
|
9
|
+
* speech toolkit. Requires no API keys or network access.
|
|
10
|
+
*
|
|
11
|
+
* Echogarden is an **optional peer dependency** (GPL v3 licensed).
|
|
12
|
+
* Users must install it explicitly: `pnpm add echogarden`.
|
|
13
|
+
*
|
|
14
|
+
* Uses dynamic import to avoid compile-time type coupling to the
|
|
15
|
+
* optional dependency.
|
|
16
|
+
*/
|
|
17
|
+
export declare function createEchogardenProvider(): TtsProvider;
|
|
18
|
+
//# sourceMappingURL=echogarden.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"echogarden.d.ts","sourceRoot":"","sources":["../../../src/tts/echogarden.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAyB,MAAM,YAAY,CAAC;AAErE;;;GAGG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO9D;AAED;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,IAAI,WAAW,CAmCtD"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks whether the `echogarden` package is available at runtime.
|
|
3
|
+
* Returns `true` if it can be imported, `false` otherwise.
|
|
4
|
+
*/
|
|
5
|
+
export async function isEchogardenAvailable() {
|
|
6
|
+
try {
|
|
7
|
+
await import("echogarden");
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* TTS provider backed by Echogarden — a TypeScript-native, offline
|
|
16
|
+
* speech toolkit. Requires no API keys or network access.
|
|
17
|
+
*
|
|
18
|
+
* Echogarden is an **optional peer dependency** (GPL v3 licensed).
|
|
19
|
+
* Users must install it explicitly: `pnpm add echogarden`.
|
|
20
|
+
*
|
|
21
|
+
* Uses dynamic import to avoid compile-time type coupling to the
|
|
22
|
+
* optional dependency.
|
|
23
|
+
*/
|
|
24
|
+
export function createEchogardenProvider() {
|
|
25
|
+
return {
|
|
26
|
+
name: "echogarden",
|
|
27
|
+
async synthesize(text, options) {
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
29
|
+
let echogarden;
|
|
30
|
+
try {
|
|
31
|
+
echogarden = await import("echogarden");
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
throw new Error("Echogarden is not installed. Install it with:\n\n" +
|
|
35
|
+
" pnpm add echogarden\n\n" +
|
|
36
|
+
"Or use a different TTS provider: --tts edge-tts, --tts openai");
|
|
37
|
+
}
|
|
38
|
+
const result = await echogarden.synthesize(text, {
|
|
39
|
+
engine: "vits",
|
|
40
|
+
voice: options.voice,
|
|
41
|
+
});
|
|
42
|
+
const rawAudio = result.audio;
|
|
43
|
+
const sampleRate = rawAudio.sampleRate ?? 22050;
|
|
44
|
+
const samples = rawAudio.audioChannels?.[0]?.length ?? 0;
|
|
45
|
+
const durationMs = Math.round((samples / sampleRate) * 1000);
|
|
46
|
+
const mp3Bytes = await echogarden.encodeRawAudioToMp3(rawAudio);
|
|
47
|
+
return {
|
|
48
|
+
audio: Buffer.from(mp3Bytes),
|
|
49
|
+
durationMs,
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=echogarden.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"echogarden.js","sourceRoot":"","sources":["../../../src/tts/echogarden.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB;IACzC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;QAC3B,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,wBAAwB;IACtC,OAAO;QACL,IAAI,EAAE,YAAY;QAElB,KAAK,CAAC,UAAU,CAAC,IAAY,EAAE,OAAmB;YAChD,8DAA8D;YAC9D,IAAI,UAAe,CAAC;YACpB,IAAI,CAAC;gBACH,UAAU,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;YAC1C,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,IAAI,KAAK,CACb,mDAAmD;oBACnD,2BAA2B;oBAC3B,+DAA+D,CAChE,CAAC;YACJ,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,UAAU,CAAC,IAAI,EAAE;gBAC/C,MAAM,EAAE,MAAM;gBACd,KAAK,EAAE,OAAO,CAAC,KAAK;aACrB,CAAC,CAAC;YAEH,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC;YAC9B,MAAM,UAAU,GAAW,QAAQ,CAAC,UAAU,IAAI,KAAK,CAAC;YACxD,MAAM,OAAO,GAAW,QAAQ,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,IAAI,CAAC,CAAC;YACjE,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,UAAU,CAAC,GAAG,IAAI,CAAC,CAAC;YAE7D,MAAM,QAAQ,GAAe,MAAM,UAAU,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAC;YAE5E,OAAO;gBACL,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAC5B,UAAU;aACX,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { TtsProvider, TtsOptions, TtsResult } from "./types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Checks whether the `echogarden` package is available at runtime.
|
|
5
|
+
* Returns `true` if it can be imported, `false` otherwise.
|
|
6
|
+
*/
|
|
7
|
+
export async function isEchogardenAvailable(): Promise<boolean> {
|
|
8
|
+
try {
|
|
9
|
+
await import("echogarden");
|
|
10
|
+
return true;
|
|
11
|
+
} catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* TTS provider backed by Echogarden — a TypeScript-native, offline
|
|
18
|
+
* speech toolkit. Requires no API keys or network access.
|
|
19
|
+
*
|
|
20
|
+
* Echogarden is an **optional peer dependency** (GPL v3 licensed).
|
|
21
|
+
* Users must install it explicitly: `pnpm add echogarden`.
|
|
22
|
+
*
|
|
23
|
+
* Uses dynamic import to avoid compile-time type coupling to the
|
|
24
|
+
* optional dependency.
|
|
25
|
+
*/
|
|
26
|
+
export function createEchogardenProvider(): TtsProvider {
|
|
27
|
+
return {
|
|
28
|
+
name: "echogarden",
|
|
29
|
+
|
|
30
|
+
async synthesize(text: string, options: TtsOptions): Promise<TtsResult> {
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
|
+
let echogarden: any;
|
|
33
|
+
try {
|
|
34
|
+
echogarden = await import("echogarden");
|
|
35
|
+
} catch {
|
|
36
|
+
throw new Error(
|
|
37
|
+
"Echogarden is not installed. Install it with:\n\n" +
|
|
38
|
+
" pnpm add echogarden\n\n" +
|
|
39
|
+
"Or use a different TTS provider: --tts edge-tts, --tts openai",
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const result = await echogarden.synthesize(text, {
|
|
44
|
+
engine: "vits",
|
|
45
|
+
voice: options.voice,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const rawAudio = result.audio;
|
|
49
|
+
const sampleRate: number = rawAudio.sampleRate ?? 22050;
|
|
50
|
+
const samples: number = rawAudio.audioChannels?.[0]?.length ?? 0;
|
|
51
|
+
const durationMs = Math.round((samples / sampleRate) * 1000);
|
|
52
|
+
|
|
53
|
+
const mp3Bytes: Uint8Array = await echogarden.encodeRawAudioToMp3(rawAudio);
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
audio: Buffer.from(mp3Bytes),
|
|
57
|
+
durationMs,
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { TtsProvider } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Checks whether the `edge-tts-universal` package is available at runtime.
|
|
4
|
+
* Returns `true` if it can be imported, `false` otherwise.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isEdgeTtsAvailable(): Promise<boolean>;
|
|
7
|
+
/**
|
|
8
|
+
* TTS provider backed by Microsoft Edge TTS via `edge-tts-universal`.
|
|
9
|
+
* Free, requires no API keys, but needs network access.
|
|
10
|
+
*
|
|
11
|
+
* `edge-tts-universal` is an **optional peer dependency** (AGPL-3.0 licensed).
|
|
12
|
+
* Users must install it explicitly: `pnpm add edge-tts-universal`.
|
|
13
|
+
*
|
|
14
|
+
* Duration is extracted from word-boundary subtitle metadata returned by
|
|
15
|
+
* the service. Falls back to a bitrate-based estimate when metadata is
|
|
16
|
+
* unavailable — same approach proven in Stigmer's narration pipeline.
|
|
17
|
+
*/
|
|
18
|
+
export declare function createEdgeTtsProvider(): TtsProvider;
|
|
19
|
+
//# sourceMappingURL=edge-tts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edge-tts.d.ts","sourceRoot":"","sources":["../../../src/tts/edge-tts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAyB,MAAM,YAAY,CAAC;AAUrE;;;GAGG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO3D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,IAAI,WAAW,CAsCnD"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const DEFAULT_VOICE = "en-US-AndrewMultilingualNeural";
|
|
2
|
+
/**
|
|
3
|
+
* Edge TTS outputs mono MP3 at 48 kbps. Used as a fallback when
|
|
4
|
+
* subtitle metadata is unavailable for precise duration.
|
|
5
|
+
*/
|
|
6
|
+
const EDGE_TTS_BITRATE_KBPS = 48;
|
|
7
|
+
/**
|
|
8
|
+
* Checks whether the `edge-tts-universal` package is available at runtime.
|
|
9
|
+
* Returns `true` if it can be imported, `false` otherwise.
|
|
10
|
+
*/
|
|
11
|
+
export async function isEdgeTtsAvailable() {
|
|
12
|
+
try {
|
|
13
|
+
await import("edge-tts-universal");
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* TTS provider backed by Microsoft Edge TTS via `edge-tts-universal`.
|
|
22
|
+
* Free, requires no API keys, but needs network access.
|
|
23
|
+
*
|
|
24
|
+
* `edge-tts-universal` is an **optional peer dependency** (AGPL-3.0 licensed).
|
|
25
|
+
* Users must install it explicitly: `pnpm add edge-tts-universal`.
|
|
26
|
+
*
|
|
27
|
+
* Duration is extracted from word-boundary subtitle metadata returned by
|
|
28
|
+
* the service. Falls back to a bitrate-based estimate when metadata is
|
|
29
|
+
* unavailable — same approach proven in Stigmer's narration pipeline.
|
|
30
|
+
*/
|
|
31
|
+
export function createEdgeTtsProvider() {
|
|
32
|
+
return {
|
|
33
|
+
name: "edge-tts",
|
|
34
|
+
async synthesize(text, options) {
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
36
|
+
let EdgeTTS;
|
|
37
|
+
try {
|
|
38
|
+
const mod = await import("edge-tts-universal");
|
|
39
|
+
EdgeTTS = mod.EdgeTTS;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
throw new Error("edge-tts-universal is not installed. Install it with:\n\n" +
|
|
43
|
+
" pnpm add edge-tts-universal\n\n" +
|
|
44
|
+
"Or use a different TTS provider: --tts echogarden, --tts openai");
|
|
45
|
+
}
|
|
46
|
+
const voice = options.voice ?? DEFAULT_VOICE;
|
|
47
|
+
const tts = new EdgeTTS(text, voice);
|
|
48
|
+
const result = await tts.synthesize();
|
|
49
|
+
const audioBuffer = Buffer.from(await result.audio.arrayBuffer());
|
|
50
|
+
let durationMs = 0;
|
|
51
|
+
if (result.subtitle.length > 0) {
|
|
52
|
+
const last = result.subtitle[result.subtitle.length - 1];
|
|
53
|
+
// offset and duration are in 100-nanosecond units
|
|
54
|
+
durationMs = Math.ceil((last.offset + last.duration) / 10_000);
|
|
55
|
+
}
|
|
56
|
+
if (durationMs === 0 && audioBuffer.length > 0) {
|
|
57
|
+
durationMs = Math.ceil((audioBuffer.length * 8) / EDGE_TTS_BITRATE_KBPS);
|
|
58
|
+
}
|
|
59
|
+
return { audio: audioBuffer, durationMs };
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=edge-tts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edge-tts.js","sourceRoot":"","sources":["../../../src/tts/edge-tts.ts"],"names":[],"mappings":"AAEA,MAAM,aAAa,GAAG,gCAAgC,CAAC;AAEvD;;;GAGG;AACH,MAAM,qBAAqB,GAAG,EAAE,CAAC;AAEjC;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;QACnC,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,qBAAqB;IACnC,OAAO;QACL,IAAI,EAAE,UAAU;QAEhB,KAAK,CAAC,UAAU,CAAC,IAAY,EAAE,OAAmB;YAChD,8DAA8D;YAC9D,IAAI,OAAY,CAAC;YACjB,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;gBAC/C,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC;YACxB,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,IAAI,KAAK,CACb,2DAA2D;oBACzD,mCAAmC;oBACnC,iEAAiE,CACpE,CAAC;YACJ,CAAC;YAED,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,aAAa,CAAC;YAC7C,MAAM,GAAG,GAAG,IAAI,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YACrC,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,UAAU,EAAE,CAAC;YAEtC,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;YAElE,IAAI,UAAU,GAAG,CAAC,CAAC;YACnB,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC/B,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;gBACzD,kDAAkD;gBAClD,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC,CAAC;YACjE,CAAC;YAED,IAAI,UAAU,KAAK,CAAC,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC/C,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,qBAAqB,CAAC,CAAC;YAC3E,CAAC;YAED,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC;QAC5C,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { TtsProvider, TtsOptions, TtsResult } from "./types.js";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_VOICE = "en-US-AndrewMultilingualNeural";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Edge TTS outputs mono MP3 at 48 kbps. Used as a fallback when
|
|
7
|
+
* subtitle metadata is unavailable for precise duration.
|
|
8
|
+
*/
|
|
9
|
+
const EDGE_TTS_BITRATE_KBPS = 48;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Checks whether the `edge-tts-universal` package is available at runtime.
|
|
13
|
+
* Returns `true` if it can be imported, `false` otherwise.
|
|
14
|
+
*/
|
|
15
|
+
export async function isEdgeTtsAvailable(): Promise<boolean> {
|
|
16
|
+
try {
|
|
17
|
+
await import("edge-tts-universal");
|
|
18
|
+
return true;
|
|
19
|
+
} catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* TTS provider backed by Microsoft Edge TTS via `edge-tts-universal`.
|
|
26
|
+
* Free, requires no API keys, but needs network access.
|
|
27
|
+
*
|
|
28
|
+
* `edge-tts-universal` is an **optional peer dependency** (AGPL-3.0 licensed).
|
|
29
|
+
* Users must install it explicitly: `pnpm add edge-tts-universal`.
|
|
30
|
+
*
|
|
31
|
+
* Duration is extracted from word-boundary subtitle metadata returned by
|
|
32
|
+
* the service. Falls back to a bitrate-based estimate when metadata is
|
|
33
|
+
* unavailable — same approach proven in Stigmer's narration pipeline.
|
|
34
|
+
*/
|
|
35
|
+
export function createEdgeTtsProvider(): TtsProvider {
|
|
36
|
+
return {
|
|
37
|
+
name: "edge-tts",
|
|
38
|
+
|
|
39
|
+
async synthesize(text: string, options: TtsOptions): Promise<TtsResult> {
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
41
|
+
let EdgeTTS: any;
|
|
42
|
+
try {
|
|
43
|
+
const mod = await import("edge-tts-universal");
|
|
44
|
+
EdgeTTS = mod.EdgeTTS;
|
|
45
|
+
} catch {
|
|
46
|
+
throw new Error(
|
|
47
|
+
"edge-tts-universal is not installed. Install it with:\n\n" +
|
|
48
|
+
" pnpm add edge-tts-universal\n\n" +
|
|
49
|
+
"Or use a different TTS provider: --tts echogarden, --tts openai",
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const voice = options.voice ?? DEFAULT_VOICE;
|
|
54
|
+
const tts = new EdgeTTS(text, voice);
|
|
55
|
+
const result = await tts.synthesize();
|
|
56
|
+
|
|
57
|
+
const audioBuffer = Buffer.from(await result.audio.arrayBuffer());
|
|
58
|
+
|
|
59
|
+
let durationMs = 0;
|
|
60
|
+
if (result.subtitle.length > 0) {
|
|
61
|
+
const last = result.subtitle[result.subtitle.length - 1];
|
|
62
|
+
// offset and duration are in 100-nanosecond units
|
|
63
|
+
durationMs = Math.ceil((last.offset + last.duration) / 10_000);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (durationMs === 0 && audioBuffer.length > 0) {
|
|
67
|
+
durationMs = Math.ceil((audioBuffer.length * 8) / EDGE_TTS_BITRATE_KBPS);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return { audio: audioBuffer, durationMs };
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { TtsProvider } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* TTS provider backed by OpenAI's text-to-speech API.
|
|
4
|
+
* Requires the `OPENAI_API_KEY` environment variable.
|
|
5
|
+
*/
|
|
6
|
+
export declare function createOpenAIProvider(): TtsProvider;
|
|
7
|
+
//# sourceMappingURL=openai.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../../src/tts/openai.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAyB,MAAM,YAAY,CAAC;AAMrE;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,WAAW,CA8ClD"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const OPENAI_TTS_URL = "https://api.openai.com/v1/audio/speech";
|
|
2
|
+
const DEFAULT_MODEL = "tts-1";
|
|
3
|
+
const DEFAULT_VOICE = "alloy";
|
|
4
|
+
/**
|
|
5
|
+
* TTS provider backed by OpenAI's text-to-speech API.
|
|
6
|
+
* Requires the `OPENAI_API_KEY` environment variable.
|
|
7
|
+
*/
|
|
8
|
+
export function createOpenAIProvider() {
|
|
9
|
+
return {
|
|
10
|
+
name: "openai",
|
|
11
|
+
async synthesize(text, options) {
|
|
12
|
+
const apiKey = process.env["OPENAI_API_KEY"];
|
|
13
|
+
if (!apiKey) {
|
|
14
|
+
throw new Error("OPENAI_API_KEY environment variable is not set.\n\n" +
|
|
15
|
+
"Set it before running narrate:\n" +
|
|
16
|
+
" export OPENAI_API_KEY=sk-...\n\n" +
|
|
17
|
+
"Or use a different TTS provider: --tts echogarden");
|
|
18
|
+
}
|
|
19
|
+
const voice = options.voice ?? DEFAULT_VOICE;
|
|
20
|
+
const response = await fetch(OPENAI_TTS_URL, {
|
|
21
|
+
method: "POST",
|
|
22
|
+
headers: {
|
|
23
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
24
|
+
"Content-Type": "application/json",
|
|
25
|
+
},
|
|
26
|
+
body: JSON.stringify({
|
|
27
|
+
model: DEFAULT_MODEL,
|
|
28
|
+
input: text,
|
|
29
|
+
voice,
|
|
30
|
+
response_format: "mp3",
|
|
31
|
+
}),
|
|
32
|
+
});
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
const body = await response.text();
|
|
35
|
+
throw new Error(`OpenAI TTS API error (${response.status}): ${body}`);
|
|
36
|
+
}
|
|
37
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
38
|
+
const audio = Buffer.from(arrayBuffer);
|
|
39
|
+
const durationMs = estimateMp3DurationMs(audio);
|
|
40
|
+
return { audio, durationMs };
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Rough MP3 duration estimate from file size and assumed bitrate.
|
|
46
|
+
* OpenAI tts-1 defaults to ~64 kbps MP3 output. This is a fallback
|
|
47
|
+
* when precise duration parsing isn't available.
|
|
48
|
+
*/
|
|
49
|
+
function estimateMp3DurationMs(mp3) {
|
|
50
|
+
const ASSUMED_BITRATE_KBPS = 64;
|
|
51
|
+
const bytes = mp3.byteLength;
|
|
52
|
+
return Math.round((bytes * 8) / ASSUMED_BITRATE_KBPS);
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=openai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.js","sourceRoot":"","sources":["../../../src/tts/openai.ts"],"names":[],"mappings":"AAEA,MAAM,cAAc,GAAG,wCAAwC,CAAC;AAChE,MAAM,aAAa,GAAG,OAAO,CAAC;AAC9B,MAAM,aAAa,GAAG,OAAO,CAAC;AAE9B;;;GAGG;AACH,MAAM,UAAU,oBAAoB;IAClC,OAAO;QACL,IAAI,EAAE,QAAQ;QAEd,KAAK,CAAC,UAAU,CAAC,IAAY,EAAE,OAAmB;YAChD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;YAC7C,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,MAAM,IAAI,KAAK,CACb,qDAAqD;oBACrD,kCAAkC;oBAClC,oCAAoC;oBACpC,mDAAmD,CACpD,CAAC;YACJ,CAAC;YAED,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,aAAa,CAAC;YAE7C,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,cAAc,EAAE;gBAC3C,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,eAAe,EAAE,UAAU,MAAM,EAAE;oBACnC,cAAc,EAAE,kBAAkB;iBACnC;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACnB,KAAK,EAAE,aAAa;oBACpB,KAAK,EAAE,IAAI;oBACX,KAAK;oBACL,eAAe,EAAE,KAAK;iBACvB,CAAC;aACH,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;gBACnC,MAAM,IAAI,KAAK,CACb,yBAAyB,QAAQ,CAAC,MAAM,MAAM,IAAI,EAAE,CACrD,CAAC;YACJ,CAAC;YAED,MAAM,WAAW,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC;YACjD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAEvC,MAAM,UAAU,GAAG,qBAAqB,CAAC,KAAK,CAAC,CAAC;YAEhD,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;QAC/B,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,SAAS,qBAAqB,CAAC,GAAW;IACxC,MAAM,oBAAoB,GAAG,EAAE,CAAC;IAChC,MAAM,KAAK,GAAG,GAAG,CAAC,UAAU,CAAC;IAC7B,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,oBAAoB,CAAC,CAAC;AACxD,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { TtsProvider, TtsOptions, TtsResult } from "./types.js";
|
|
2
|
+
|
|
3
|
+
const OPENAI_TTS_URL = "https://api.openai.com/v1/audio/speech";
|
|
4
|
+
const DEFAULT_MODEL = "tts-1";
|
|
5
|
+
const DEFAULT_VOICE = "alloy";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* TTS provider backed by OpenAI's text-to-speech API.
|
|
9
|
+
* Requires the `OPENAI_API_KEY` environment variable.
|
|
10
|
+
*/
|
|
11
|
+
export function createOpenAIProvider(): TtsProvider {
|
|
12
|
+
return {
|
|
13
|
+
name: "openai",
|
|
14
|
+
|
|
15
|
+
async synthesize(text: string, options: TtsOptions): Promise<TtsResult> {
|
|
16
|
+
const apiKey = process.env["OPENAI_API_KEY"];
|
|
17
|
+
if (!apiKey) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
"OPENAI_API_KEY environment variable is not set.\n\n" +
|
|
20
|
+
"Set it before running narrate:\n" +
|
|
21
|
+
" export OPENAI_API_KEY=sk-...\n\n" +
|
|
22
|
+
"Or use a different TTS provider: --tts echogarden",
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const voice = options.voice ?? DEFAULT_VOICE;
|
|
27
|
+
|
|
28
|
+
const response = await fetch(OPENAI_TTS_URL, {
|
|
29
|
+
method: "POST",
|
|
30
|
+
headers: {
|
|
31
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
32
|
+
"Content-Type": "application/json",
|
|
33
|
+
},
|
|
34
|
+
body: JSON.stringify({
|
|
35
|
+
model: DEFAULT_MODEL,
|
|
36
|
+
input: text,
|
|
37
|
+
voice,
|
|
38
|
+
response_format: "mp3",
|
|
39
|
+
}),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
const body = await response.text();
|
|
44
|
+
throw new Error(
|
|
45
|
+
`OpenAI TTS API error (${response.status}): ${body}`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
50
|
+
const audio = Buffer.from(arrayBuffer);
|
|
51
|
+
|
|
52
|
+
const durationMs = estimateMp3DurationMs(audio);
|
|
53
|
+
|
|
54
|
+
return { audio, durationMs };
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Rough MP3 duration estimate from file size and assumed bitrate.
|
|
61
|
+
* OpenAI tts-1 defaults to ~64 kbps MP3 output. This is a fallback
|
|
62
|
+
* when precise duration parsing isn't available.
|
|
63
|
+
*/
|
|
64
|
+
function estimateMp3DurationMs(mp3: Buffer): number {
|
|
65
|
+
const ASSUMED_BITRATE_KBPS = 64;
|
|
66
|
+
const bytes = mp3.byteLength;
|
|
67
|
+
return Math.round((bytes * 8) / ASSUMED_BITRATE_KBPS);
|
|
68
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { TtsProvider } from "./types.js";
|
|
2
|
+
export type TtsProviderName = "echogarden" | "edge-tts" | "openai";
|
|
3
|
+
/**
|
|
4
|
+
* Resolve a TTS provider by name.
|
|
5
|
+
*
|
|
6
|
+
* When `providerName` is `"echogarden"` (the default), verifies
|
|
7
|
+
* the optional peer dependency is installed before returning it.
|
|
8
|
+
* Falls back to a clear error message guiding the user.
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveProvider(providerName: string): Promise<TtsProvider>;
|
|
11
|
+
//# sourceMappingURL=resolve-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-provider.d.ts","sourceRoot":"","sources":["../../../src/tts/resolve-provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAK9C,MAAM,MAAM,eAAe,GAAG,YAAY,GAAG,UAAU,GAAG,QAAQ,CAAC;AAQnE;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAwChF"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { createEchogardenProvider, isEchogardenAvailable } from "./echogarden.js";
|
|
2
|
+
import { createEdgeTtsProvider, isEdgeTtsAvailable } from "./edge-tts.js";
|
|
3
|
+
import { createOpenAIProvider } from "./openai.js";
|
|
4
|
+
const KNOWN_PROVIDERS = new Set([
|
|
5
|
+
"echogarden",
|
|
6
|
+
"edge-tts",
|
|
7
|
+
"openai",
|
|
8
|
+
]);
|
|
9
|
+
/**
|
|
10
|
+
* Resolve a TTS provider by name.
|
|
11
|
+
*
|
|
12
|
+
* When `providerName` is `"echogarden"` (the default), verifies
|
|
13
|
+
* the optional peer dependency is installed before returning it.
|
|
14
|
+
* Falls back to a clear error message guiding the user.
|
|
15
|
+
*/
|
|
16
|
+
export async function resolveProvider(providerName) {
|
|
17
|
+
if (!KNOWN_PROVIDERS.has(providerName)) {
|
|
18
|
+
throw new Error(`Unknown TTS provider: "${providerName}". ` +
|
|
19
|
+
`Supported providers: ${[...KNOWN_PROVIDERS].join(", ")}.`);
|
|
20
|
+
}
|
|
21
|
+
if (providerName === "openai") {
|
|
22
|
+
return createOpenAIProvider();
|
|
23
|
+
}
|
|
24
|
+
if (providerName === "edge-tts") {
|
|
25
|
+
const available = await isEdgeTtsAvailable();
|
|
26
|
+
if (!available) {
|
|
27
|
+
throw new Error("edge-tts-universal is not installed. To use free online TTS:\n\n" +
|
|
28
|
+
" pnpm add edge-tts-universal\n\n" +
|
|
29
|
+
"Or use a different provider:\n\n" +
|
|
30
|
+
" scenar narrate <file> --tts echogarden (offline, pnpm add echogarden)\n" +
|
|
31
|
+
" scenar narrate <file> --tts openai (requires OPENAI_API_KEY)");
|
|
32
|
+
}
|
|
33
|
+
return createEdgeTtsProvider();
|
|
34
|
+
}
|
|
35
|
+
const available = await isEchogardenAvailable();
|
|
36
|
+
if (!available) {
|
|
37
|
+
throw new Error("Echogarden is not installed. To use free offline TTS:\n\n" +
|
|
38
|
+
" pnpm add echogarden\n\n" +
|
|
39
|
+
"Or use free online TTS (requires network):\n\n" +
|
|
40
|
+
" scenar narrate <file> --tts edge-tts\n" +
|
|
41
|
+
" pnpm add edge-tts-universal\n\n" +
|
|
42
|
+
"Or use OpenAI TTS (requires API key):\n\n" +
|
|
43
|
+
" scenar narrate <file> --tts openai");
|
|
44
|
+
}
|
|
45
|
+
return createEchogardenProvider();
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=resolve-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-provider.js","sourceRoot":"","sources":["../../../src/tts/resolve-provider.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAClF,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAC1E,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAInD,MAAM,eAAe,GAAwB,IAAI,GAAG,CAAkB;IACpE,YAAY;IACZ,UAAU;IACV,QAAQ;CACT,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,YAAoB;IACxD,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,KAAK,CACb,0BAA0B,YAAY,KAAK;YAC3C,wBAAwB,CAAC,GAAG,eAAe,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAC3D,CAAC;IACJ,CAAC;IAED,IAAI,YAAY,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,oBAAoB,EAAE,CAAC;IAChC,CAAC;IAED,IAAI,YAAY,KAAK,UAAU,EAAE,CAAC;QAChC,MAAM,SAAS,GAAG,MAAM,kBAAkB,EAAE,CAAC;QAC7C,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CACb,kEAAkE;gBAClE,mCAAmC;gBACnC,kCAAkC;gBAClC,6EAA6E;gBAC7E,uEAAuE,CACxE,CAAC;QACJ,CAAC;QACD,OAAO,qBAAqB,EAAE,CAAC;IACjC,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,qBAAqB,EAAE,CAAC;IAChD,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CACb,2DAA2D;YAC3D,2BAA2B;YAC3B,gDAAgD;YAChD,0CAA0C;YAC1C,mCAAmC;YACnC,2CAA2C;YAC3C,sCAAsC,CACvC,CAAC;IACJ,CAAC;IAED,OAAO,wBAAwB,EAAE,CAAC;AACpC,CAAC"}
|