@glissade/narrate 0.8.1-pre.0 → 0.8.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/dist/providers.d.ts +0 -13
- package/dist/providers.js +51 -12
- package/package.json +3 -3
package/dist/providers.d.ts
CHANGED
|
@@ -71,19 +71,6 @@ declare function piperProvider(opts?: {
|
|
|
71
71
|
/** PCM16 mono WAV from float samples in [-1, 1]. Round-to-nearest → deterministic. */
|
|
72
72
|
declare function floatToWav(samples: Float32Array, sampleRate: number): Buffer;
|
|
73
73
|
type KokoroDtype = 'fp32' | 'fp16' | 'q8' | 'q4' | 'q4f16';
|
|
74
|
-
/**
|
|
75
|
-
* Apache-2.0 82M neural TTS — markedly more natural than espeak/piper, fully
|
|
76
|
-
* offline on CPU via onnxruntime, no API key. Pure-Node through `kokoro-js`
|
|
77
|
-
* (Transformers.js), so unlike piper there is no `pip install` / external
|
|
78
|
-
* binary; `kokoro-js` is an OPTIONAL peer dep, lazy-loaded here.
|
|
79
|
-
*
|
|
80
|
-
* DETERMINISTIC by construction: inference takes tokenized phonemes + a FIXED
|
|
81
|
-
* voice/style embedding (not diffusion-sampled per call), so the same text →
|
|
82
|
-
* byte-identical PCM — no noise to zero out (piper's trick). `version()` pins
|
|
83
|
-
* the lib version + model + dtype, so any of those moving invalidates the
|
|
84
|
-
* cache. The model (~q8 92MB / fp32 326MB) downloads + caches on first use; it
|
|
85
|
-
* stays out of the bundle and the determinism-critical path.
|
|
86
|
-
*/
|
|
87
74
|
declare function kokoroProvider(opts?: {
|
|
88
75
|
model?: string;
|
|
89
76
|
voice?: string;
|
package/dist/providers.js
CHANGED
|
@@ -5,6 +5,7 @@ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "
|
|
|
5
5
|
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
6
6
|
import { homedir, tmpdir } from "node:os";
|
|
7
7
|
import { spawnSync } from "node:child_process";
|
|
8
|
+
import { pathToFileURL } from "node:url";
|
|
8
9
|
//#region src/providers.ts
|
|
9
10
|
/**
|
|
10
11
|
* '@glissade/narrate/providers' — the Node-only prepare side. Provider calls
|
|
@@ -264,16 +265,61 @@ const KOKORO_DEFAULT_VOICE = "af_heart";
|
|
|
264
265
|
* cache. The model (~q8 92MB / fp32 326MB) downloads + caches on first use; it
|
|
265
266
|
* stays out of the bundle and the determinism-critical path.
|
|
266
267
|
*/
|
|
268
|
+
/** kokoro-js version read by walking up from its entry (it does not export
|
|
269
|
+
* `./package.json`, so the subpath can't be resolved directly). */
|
|
270
|
+
function kokoroVersionFrom(entry) {
|
|
271
|
+
let dir = dirname(entry);
|
|
272
|
+
for (let i = 0; i < 8; i++) {
|
|
273
|
+
const p = join(dir, "package.json");
|
|
274
|
+
if (existsSync(p)) try {
|
|
275
|
+
const j = JSON.parse(readFileSync(p, "utf8"));
|
|
276
|
+
if (j.name === "kokoro-js" && j.version) return j.version;
|
|
277
|
+
} catch {}
|
|
278
|
+
const up = dirname(dir);
|
|
279
|
+
if (up === dir) break;
|
|
280
|
+
dir = up;
|
|
281
|
+
}
|
|
282
|
+
return "unknown";
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Resolve the OPTIONAL peer `kokoro-js` from the USER'S project first. Under
|
|
286
|
+
* pnpm's isolated layout a peer is NOT linked into `@glissade/narrate`'s own
|
|
287
|
+
* store dir, so a bare `import('kokoro-js')` from this module fails; resolving
|
|
288
|
+
* relative to `process.cwd()` (where the user ran `add kokoro-js`) finds it.
|
|
289
|
+
* Falls back to this module for hoisted/global installs. Returns a `file://`
|
|
290
|
+
* entry URL (so the dynamic import is never bundled) + the resolved version.
|
|
291
|
+
* Throws a NarrationError that carries the REAL resolution error.
|
|
292
|
+
*/
|
|
293
|
+
function resolveKokoro() {
|
|
294
|
+
const bases = [pathToFileURL(join(process.cwd(), "package.json")).href, import.meta.url];
|
|
295
|
+
let lastErr;
|
|
296
|
+
for (const base of bases) try {
|
|
297
|
+
const entry = createRequire(base).resolve("kokoro-js");
|
|
298
|
+
return {
|
|
299
|
+
entryUrl: pathToFileURL(entry).href,
|
|
300
|
+
version: kokoroVersionFrom(entry)
|
|
301
|
+
};
|
|
302
|
+
} catch (e) {
|
|
303
|
+
lastErr = e;
|
|
304
|
+
}
|
|
305
|
+
throw new NarrationError(`kokoro-js could not be resolved from ${process.cwd()} (${lastErr?.code ?? "error"}: ${lastErr?.message ?? "not found"}) — install it in your project (npm / pnpm / yarn add kokoro-js; pnpm users must also allow its native build scripts — see the narration docs), or use --provider piper/espeak/openai`);
|
|
306
|
+
}
|
|
267
307
|
function kokoroProvider(opts = {}) {
|
|
268
308
|
const modelId = opts.model ?? KOKORO_MODEL;
|
|
269
309
|
const dtype = opts.dtype ?? "q8";
|
|
270
310
|
let loaded = null;
|
|
271
311
|
const loadLib = async () => {
|
|
312
|
+
const { entryUrl } = resolveKokoro();
|
|
313
|
+
let mod;
|
|
272
314
|
try {
|
|
273
|
-
|
|
274
|
-
} catch {
|
|
275
|
-
|
|
315
|
+
mod = await import(entryUrl);
|
|
316
|
+
} catch (e) {
|
|
317
|
+
const err = e;
|
|
318
|
+
throw new NarrationError(`kokoro-js failed to load from ${entryUrl} (${err?.code ?? "error"}: ${err?.message ?? String(e)}) — ensure kokoro-js and onnxruntime-node are installed, or use --provider piper/espeak/openai`);
|
|
276
319
|
}
|
|
320
|
+
const lib = mod["KokoroTTS"] ? mod : mod["default"];
|
|
321
|
+
if (!lib?.KokoroTTS) throw new NarrationError(`kokoro-js loaded but exposes no KokoroTTS export (from ${entryUrl})`);
|
|
322
|
+
return lib;
|
|
277
323
|
};
|
|
278
324
|
const getModel = () => loaded ??= loadLib().then((k) => k.KokoroTTS.from_pretrained(modelId, {
|
|
279
325
|
dtype,
|
|
@@ -282,15 +328,8 @@ function kokoroProvider(opts = {}) {
|
|
|
282
328
|
return {
|
|
283
329
|
id: "kokoro",
|
|
284
330
|
version: () => {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const req = createRequire(import.meta.url);
|
|
288
|
-
const pkg = JSON.parse(readFileSync(req.resolve("kokoro-js/package.json"), "utf8"));
|
|
289
|
-
if (pkg.version) lib = `kokoro-js ${pkg.version}`;
|
|
290
|
-
} catch {
|
|
291
|
-
throw new NarrationError("kokoro-js not found — `npm install kokoro-js` (it pulls onnxruntime-node), or use --provider piper/espeak/openai");
|
|
292
|
-
}
|
|
293
|
-
return Promise.resolve(`${lib} ${basename(modelId)} dtype=${dtype}`);
|
|
331
|
+
const { version } = resolveKokoro();
|
|
332
|
+
return Promise.resolve(`kokoro-js ${version} ${basename(modelId)} dtype=${dtype}`);
|
|
294
333
|
},
|
|
295
334
|
synthesize: async (req) => {
|
|
296
335
|
const tts = await getModel();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glissade/narrate",
|
|
3
|
-
"version": "0.8.1
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "glissade narration + captions: TTS at prepare time (gs narrate), deterministic caching, narration-anchored timeline beats, and captions as plain tracks. Render stays offline.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"dist"
|
|
20
20
|
],
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@glissade/core": "0.8.1
|
|
23
|
-
"@glissade/scene": "0.8.1
|
|
22
|
+
"@glissade/core": "0.8.1",
|
|
23
|
+
"@glissade/scene": "0.8.1"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
26
|
"kokoro-js": "^1.2.0"
|