@glissade/narrate 0.6.0 → 0.6.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 +14 -1
- package/dist/providers.js +31 -6
- package/package.json +3 -3
package/dist/providers.d.ts
CHANGED
|
@@ -50,8 +50,21 @@ declare function openaiProvider(opts?: {
|
|
|
50
50
|
* (`{ noiseScale: 0.667, noiseWScale: 0.8 }`) and wire via `providerImpl`.
|
|
51
51
|
* The noise mode is part of `version()`, so changing it invalidates the cache.
|
|
52
52
|
*/
|
|
53
|
+
/**
|
|
54
|
+
* Resolve a piper voice to something piper-tts 1.x can actually open. piper's
|
|
55
|
+
* `--model` wants a filesystem PATH to the `.onnx`, or a downloadable voice KEY
|
|
56
|
+
* — it does NOT search for a bare `.onnx` filename. So: an existing path is used
|
|
57
|
+
* as-is (absolutized); a bare `<name>`/`<name>.onnx` is looked up under the
|
|
58
|
+
* voices dir (`voicesDir` option → `PIPER_VOICES` env → `~/.local/share/piper-voices`);
|
|
59
|
+
* a `.onnx` name that resolves nowhere is a clear error; a bare key with no
|
|
60
|
+
* `.onnx` is passed through so piper can resolve/download it.
|
|
61
|
+
*/
|
|
62
|
+
declare function resolvePiperVoice(model: string, voicesDir?: string): string;
|
|
63
|
+
/** Surface the TAIL of a child's stderr — Python tracebacks put the real exception last. */
|
|
64
|
+
declare function stderrTail(stderr: unknown, max?: number): string;
|
|
53
65
|
declare function piperProvider(opts?: {
|
|
54
66
|
model?: string;
|
|
67
|
+
voicesDir?: string;
|
|
55
68
|
noiseScale?: number;
|
|
56
69
|
noiseWScale?: number;
|
|
57
70
|
}): TtsProvider;
|
|
@@ -186,4 +199,4 @@ declare function synthesizeScript(scriptPath: string, opts?: SynthesizeOptions):
|
|
|
186
199
|
/** Resolve `<scene>.narration.json` for a scene-module path (or accept the script itself). */
|
|
187
200
|
declare function scriptPathFor(input: string): string;
|
|
188
201
|
//#endregion
|
|
189
|
-
export { AlignRequest, Aligner, SynthesizeOptions, SynthesizeResult, TtsProvider, TtsRequest, TtsResult, VoskAlignWord, alignerById, cacheKey, espeakProvider, fakeProvider, heuristicAligner, heuristicWords, interpolateMissing, mapAsrToScript, openaiProvider, piperProvider, providerById, scriptPathFor, synthesizeScript, voskAligner, wavDuration };
|
|
202
|
+
export { AlignRequest, Aligner, SynthesizeOptions, SynthesizeResult, TtsProvider, TtsRequest, TtsResult, VoskAlignWord, alignerById, cacheKey, espeakProvider, fakeProvider, heuristicAligner, heuristicWords, interpolateMissing, mapAsrToScript, openaiProvider, piperProvider, providerById, resolvePiperVoice, scriptPathFor, stderrTail, synthesizeScript, voskAligner, wavDuration };
|
package/dist/providers.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { NarrationError, isPause } from "./index.js";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { basename, dirname, join } from "node:path";
|
|
5
|
-
import { tmpdir } from "node:os";
|
|
4
|
+
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
5
|
+
import { homedir, tmpdir } from "node:os";
|
|
6
6
|
import { spawnSync } from "node:child_process";
|
|
7
7
|
//#region src/providers.ts
|
|
8
8
|
/**
|
|
@@ -147,6 +147,30 @@ function openaiProvider(opts = {}) {
|
|
|
147
147
|
* (`{ noiseScale: 0.667, noiseWScale: 0.8 }`) and wire via `providerImpl`.
|
|
148
148
|
* The noise mode is part of `version()`, so changing it invalidates the cache.
|
|
149
149
|
*/
|
|
150
|
+
/**
|
|
151
|
+
* Resolve a piper voice to something piper-tts 1.x can actually open. piper's
|
|
152
|
+
* `--model` wants a filesystem PATH to the `.onnx`, or a downloadable voice KEY
|
|
153
|
+
* — it does NOT search for a bare `.onnx` filename. So: an existing path is used
|
|
154
|
+
* as-is (absolutized); a bare `<name>`/`<name>.onnx` is looked up under the
|
|
155
|
+
* voices dir (`voicesDir` option → `PIPER_VOICES` env → `~/.local/share/piper-voices`);
|
|
156
|
+
* a `.onnx` name that resolves nowhere is a clear error; a bare key with no
|
|
157
|
+
* `.onnx` is passed through so piper can resolve/download it.
|
|
158
|
+
*/
|
|
159
|
+
function resolvePiperVoice(model, voicesDir) {
|
|
160
|
+
if (existsSync(model)) return resolve(model);
|
|
161
|
+
if (isAbsolute(model)) return model;
|
|
162
|
+
const dir = voicesDir ?? process.env["PIPER_VOICES"] ?? join(homedir(), ".local", "share", "piper-voices");
|
|
163
|
+
const named = model.endsWith(".onnx") ? model : `${model}.onnx`;
|
|
164
|
+
for (const cand of [join(dir, model), join(dir, named)]) if (existsSync(cand)) return resolve(cand);
|
|
165
|
+
if (model.endsWith(".onnx")) throw new NarrationError(`piper voice '${model}' not found — it is not a path and is absent from the voices dir '${dir}'. Put the .onnx there, pass an absolute path as the voice, or set PIPER_VOICES / { voicesDir }.`);
|
|
166
|
+
return model;
|
|
167
|
+
}
|
|
168
|
+
/** Surface the TAIL of a child's stderr — Python tracebacks put the real exception last. */
|
|
169
|
+
function stderrTail(stderr, max = 400) {
|
|
170
|
+
const s = (stderr == null ? "" : String(stderr)).trim();
|
|
171
|
+
if (!s) return "no output";
|
|
172
|
+
return s.length > max ? `…${s.slice(-max)}` : s;
|
|
173
|
+
}
|
|
150
174
|
function piperProvider(opts = {}) {
|
|
151
175
|
const noiseScale = opts.noiseScale ?? 0;
|
|
152
176
|
const noiseWScale = opts.noiseWScale ?? 0;
|
|
@@ -168,8 +192,9 @@ function piperProvider(opts = {}) {
|
|
|
168
192
|
].filter(Boolean).join(" "));
|
|
169
193
|
},
|
|
170
194
|
synthesize: (req) => {
|
|
171
|
-
const
|
|
172
|
-
if (!
|
|
195
|
+
const raw = req.voice ?? opts.model;
|
|
196
|
+
if (!raw) throw new NarrationError("piper needs a voice model (.onnx) — pass { model }, or set the segment voice to its path or name");
|
|
197
|
+
const model = resolvePiperVoice(raw, opts.voicesDir);
|
|
173
198
|
const tag = createHash("sha256").update(req.text).digest("hex").slice(0, 8);
|
|
174
199
|
const out = join(tmpdir(), `glissade-piper-${process.pid}-${tag}.wav`);
|
|
175
200
|
const args = [
|
|
@@ -188,7 +213,7 @@ function piperProvider(opts = {}) {
|
|
|
188
213
|
maxBuffer: 64 * 1024 * 1024
|
|
189
214
|
});
|
|
190
215
|
try {
|
|
191
|
-
if (r.status !== 0 || !existsSync(out)) throw new NarrationError(`piper failed: ${r.stderr
|
|
216
|
+
if (r.status !== 0 || !existsSync(out)) throw new NarrationError(`piper failed: ${stderrTail(r.stderr)}`);
|
|
192
217
|
const wav = readFileSync(out);
|
|
193
218
|
return Promise.resolve({
|
|
194
219
|
wav,
|
|
@@ -533,4 +558,4 @@ function scriptPathFor(input) {
|
|
|
533
558
|
return candidate;
|
|
534
559
|
}
|
|
535
560
|
//#endregion
|
|
536
|
-
export { alignerById, cacheKey, espeakProvider, fakeProvider, heuristicAligner, heuristicWords, interpolateMissing, mapAsrToScript, openaiProvider, piperProvider, providerById, scriptPathFor, synthesizeScript, voskAligner, wavDuration };
|
|
561
|
+
export { alignerById, cacheKey, espeakProvider, fakeProvider, heuristicAligner, heuristicWords, interpolateMissing, mapAsrToScript, openaiProvider, piperProvider, providerById, resolvePiperVoice, scriptPathFor, stderrTail, synthesizeScript, voskAligner, wavDuration };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glissade/narrate",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.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.6.
|
|
23
|
-
"@glissade/scene": "0.6.
|
|
22
|
+
"@glissade/core": "0.6.1",
|
|
23
|
+
"@glissade/scene": "0.6.1"
|
|
24
24
|
},
|
|
25
25
|
"repository": {
|
|
26
26
|
"type": "git",
|