mia-narrative 1.0.9 → 1.1.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 +20 -20
- package/bin/mia-narrative.ts +4 -0
- package/dist/bin/mia-narrative.js +3 -0
- package/dist/bin/mia-narrative.js.map +1 -1
- package/dist/src/engines/piper.d.ts.map +1 -1
- package/dist/src/engines/piper.js +21 -13
- package/dist/src/engines/piper.js.map +1 -1
- package/dist/src/utils/file-reader.d.ts +1 -0
- package/dist/src/utils/file-reader.d.ts.map +1 -1
- package/dist/src/utils/file-reader.js +9 -0
- package/dist/src/utils/file-reader.js.map +1 -1
- package/dist/src/utils/prerequisites.d.ts +59 -0
- package/dist/src/utils/prerequisites.d.ts.map +1 -0
- package/dist/src/utils/prerequisites.js +218 -0
- package/dist/src/utils/prerequisites.js.map +1 -0
- package/package.json +1 -1
- package/scripts/fn_tts.sh +89 -0
- package/scripts/setup.js +44 -8
- package/src/engines/piper.ts +19 -12
- package/src/utils/file-reader.ts +12 -0
- package/src/utils/prerequisites.ts +245 -0
package/README.md
CHANGED
|
@@ -31,49 +31,49 @@
|
|
|
31
31
|
|
|
32
32
|
### Get Started
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
\`\`\`bash
|
|
35
35
|
git clone <repo>
|
|
36
36
|
cd cli
|
|
37
37
|
npm install
|
|
38
38
|
npm run build
|
|
39
39
|
npm link # Makes 'mia-narrative' available globally
|
|
40
|
-
|
|
40
|
+
\`\`\`
|
|
41
41
|
|
|
42
42
|
### Optional: Setup Professional Voices
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
\`\`\`bash
|
|
45
45
|
npm run setup
|
|
46
46
|
# Automatically downloads Piper models (one-time, ~380MB)
|
|
47
|
-
|
|
47
|
+
\`\`\`
|
|
48
48
|
|
|
49
49
|
### Optional: ElevenLabs (Cloud Voices)
|
|
50
50
|
|
|
51
51
|
Get an API key from [elevenlabs.io](https://elevenlabs.io):
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
\`\`\`bash
|
|
54
54
|
export ELEVENLABS_API_KEY="your_key_here"
|
|
55
55
|
mia-narrative g -E elevenlabs -T "Hello" -O output.mp3
|
|
56
|
-
|
|
56
|
+
\`\`\`
|
|
57
57
|
|
|
58
58
|
## Quick Start
|
|
59
59
|
|
|
60
60
|
**Simplest command** (uses system voice):
|
|
61
|
-
|
|
61
|
+
\`\`\`bash
|
|
62
62
|
mia-narrative g -T "Hello, world!"
|
|
63
|
-
|
|
63
|
+
\`\`\`
|
|
64
64
|
|
|
65
65
|
**With a specific voice** (Miette with British accent):
|
|
66
|
-
|
|
66
|
+
\`\`\`bash
|
|
67
67
|
mia-narrative g -E piper -v miette -T "What a lovely day!" -O story.mp3
|
|
68
|
-
|
|
68
|
+
\`\`\`
|
|
69
69
|
|
|
70
70
|
**From a text file** with effects:
|
|
71
|
-
|
|
71
|
+
\`\`\`bash
|
|
72
72
|
mia-narrative g -E piper -v mia -F chapter.txt --pitch 1.1 --speed 0.95 -O chapter.mp3
|
|
73
|
-
|
|
73
|
+
\`\`\`
|
|
74
74
|
|
|
75
75
|
**Professional narrator** with full effects:
|
|
76
|
-
|
|
76
|
+
\`\`\`bash
|
|
77
77
|
mia-narrative g \
|
|
78
78
|
-E piper \
|
|
79
79
|
-v mia \
|
|
@@ -83,7 +83,7 @@ mia-narrative g \
|
|
|
83
83
|
--reverb 0.3 \
|
|
84
84
|
--compression 0.4 \
|
|
85
85
|
-O final.mp3
|
|
86
|
-
|
|
86
|
+
\`\`\`
|
|
87
87
|
|
|
88
88
|
### Command & Option Reference
|
|
89
89
|
|
|
@@ -98,7 +98,7 @@ mia-narrative g \
|
|
|
98
98
|
|
|
99
99
|
See which narrators are available:
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
\`\`\`bash
|
|
102
102
|
# See system voices (quick, no setup)
|
|
103
103
|
mia-narrative voices --engine system
|
|
104
104
|
|
|
@@ -107,7 +107,7 @@ mia-narrative voices --engine piper
|
|
|
107
107
|
|
|
108
108
|
# See ElevenLabs options (requires API key)
|
|
109
109
|
mia-narrative voices --engine elevenlabs
|
|
110
|
-
|
|
110
|
+
\`\`\`
|
|
111
111
|
|
|
112
112
|
Output shows voice ID, personality, and which model it uses.
|
|
113
113
|
|
|
@@ -115,10 +115,10 @@ Output shows voice ID, personality, and which model it uses.
|
|
|
115
115
|
|
|
116
116
|
Get access to professional voices with full control:
|
|
117
117
|
|
|
118
|
-
|
|
118
|
+
\`\`\`bash
|
|
119
119
|
npm run setup
|
|
120
120
|
# Downloads Mia, Miette, Seraphine, Jeremy, ResoNova, Zephyr, Echo, Atlas
|
|
121
|
-
|
|
121
|
+
\`\`\`
|
|
122
122
|
|
|
123
123
|
## Audio Parameters
|
|
124
124
|
|
|
@@ -153,8 +153,8 @@ npm run setup
|
|
|
153
153
|
|
|
154
154
|
## Contributing & Development
|
|
155
155
|
|
|
156
|
-
|
|
156
|
+
\`\`\`bash
|
|
157
157
|
npm run build # Compile TypeScript
|
|
158
158
|
npm run dev -- g -T "test" # Run in dev mode
|
|
159
159
|
npm test # Run tests
|
|
160
|
-
|
|
160
|
+
\`\`\`
|
package/bin/mia-narrative.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { join, dirname } from 'path';
|
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
7
|
import { generateCommand } from '../src/commands/generate.js';
|
|
8
8
|
import { voicesCommand } from '../src/commands/voices.js';
|
|
9
|
+
import { ensureMpg123 } from '../src/utils/prerequisites.js';
|
|
9
10
|
|
|
10
11
|
// Get version from package.json
|
|
11
12
|
let version = '1.0.0'; // fallback
|
|
@@ -20,6 +21,9 @@ try {
|
|
|
20
21
|
// If we can't read package.json, use fallback version
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
// Check for mpg123 on startup (try to install if missing)
|
|
25
|
+
ensureMpg123();
|
|
26
|
+
|
|
23
27
|
const program = new Command();
|
|
24
28
|
|
|
25
29
|
program
|
|
@@ -5,6 +5,7 @@ import { join, dirname } from 'path';
|
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { generateCommand } from '../src/commands/generate.js';
|
|
7
7
|
import { voicesCommand } from '../src/commands/voices.js';
|
|
8
|
+
import { ensureMpg123 } from '../src/utils/prerequisites.js';
|
|
8
9
|
// Get version from package.json
|
|
9
10
|
let version = '1.0.0'; // fallback
|
|
10
11
|
try {
|
|
@@ -18,6 +19,8 @@ try {
|
|
|
18
19
|
catch {
|
|
19
20
|
// If we can't read package.json, use fallback version
|
|
20
21
|
}
|
|
22
|
+
// Check for mpg123 on startup (try to install if missing)
|
|
23
|
+
ensureMpg123();
|
|
21
24
|
const program = new Command();
|
|
22
25
|
program
|
|
23
26
|
.name('mia-narrative')
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mia-narrative.js","sourceRoot":"","sources":["../../bin/mia-narrative.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"mia-narrative.js","sourceRoot":"","sources":["../../bin/mia-narrative.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAE7D,gCAAgC;AAChC,IAAI,OAAO,GAAG,OAAO,CAAC,CAAC,WAAW;AAClC,IAAI,CAAC;IACH,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACtC,oDAAoD;IACpD,MAAM,eAAe,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;IACpE,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC,CAAC;IACvE,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC;AAChC,CAAC;AAAC,MAAM,CAAC;IACP,sDAAsD;AACxD,CAAC;AAED,0DAA0D;AAC1D,YAAY,EAAE,CAAC;AAEf,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,eAAe,CAAC;KACrB,WAAW,CAAC,mDAAmD,CAAC;KAChE,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO;KACJ,OAAO,CAAC,UAAU,CAAC;KACnB,KAAK,CAAC,GAAG,CAAC;KACV,WAAW,CAAC,0BAA0B,CAAC;KACvC,MAAM,CAAC,mBAAmB,EAAE,2BAA2B,CAAC;KACxD,MAAM,CAAC,mBAAmB,EAAE,qBAAqB,CAAC;KAClD,MAAM,CACL,qBAAqB,EACrB,+CAA+C,EAC/C,QAAQ,CACT;KACA,MAAM,CAAC,oBAAoB,EAAE,iBAAiB,CAAC;KAC/C,MAAM,CACL,qBAAqB,EACrB,4CAA4C,CAC7C;KACA,MAAM,CACL,kBAAkB,EAClB,4BAA4B,EAC5B,UAAU,CACX;KACA,MAAM,CACL,kBAAkB,EAClB,sBAAsB,EACtB,UAAU,CACX;KACA,MAAM,CACL,mBAAmB,EACnB,uBAAuB,EACvB,UAAU,CACX;KACA,MAAM,CACL,qBAAqB,EACrB,yBAAyB,EACzB,UAAU,CACX;KACA,MAAM,CACL,mBAAmB,EACnB,uBAAuB,EACvB,UAAU,CACX;KACA,MAAM,CACL,iBAAiB,EACjB,qBAAqB,EACrB,UAAU,CACX;KACA,MAAM,CACL,wBAAwB,EACxB,4BAA4B,EAC5B,UAAU,CACX;KACA,MAAM,CACL,kBAAkB,EAClB,6BAA6B,EAC7B,UAAU,CACX;KACA,MAAM,CAAC,eAAe,CAAC,CAAC;AAE3B,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,6CAA6C,CAAC;KAC1D,MAAM,CACL,iBAAiB,EACjB,2DAA2D,EAC3D,QAAQ,CACT;KACA,MAAM,CACL,mBAAmB,EACnB,mCAAmC,EACnC,OAAO,CACR;KACA,MAAM,CAAC,aAAa,CAAC,CAAC;AAEzB,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"piper.d.ts","sourceRoot":"","sources":["../../../src/engines/piper.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,oBAAoB,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"piper.d.ts","sourceRoot":"","sources":["../../../src/engines/piper.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,oBAAoB,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAIpF,qBAAa,WAAY,SAAQ,SAAS;IACxC,OAAO,CAAC,SAAS,CAAmB;IACpC,OAAO,CAAC,SAAS,CAAc;IAEzB,UAAU,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAaxD,OAAO,CAAC,cAAc;IAMhB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC;IAyD7D,SAAS,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;IAiB7B,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAerC,OAAO,IAAI,MAAM;IAIjB,OAAO,CAAC,QAAQ;CA4CjB"}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { spawn, execSync } from 'child_process';
|
|
2
2
|
import { writeFileSync, unlinkSync, readFileSync, existsSync } from 'fs';
|
|
3
|
-
import { tmpdir
|
|
3
|
+
import { tmpdir } from 'os';
|
|
4
4
|
import { join } from 'path';
|
|
5
5
|
import { randomUUID } from 'crypto';
|
|
6
6
|
import { Logger } from '../utils/logger.js';
|
|
7
7
|
import { TTSEngine } from './base.js';
|
|
8
8
|
import { getVoiceProfile, VOICE_PROFILES } from '../config/voices.js';
|
|
9
|
+
import { getPiperPath, getModelPath, getPiperMissingMessage } from '../utils/prerequisites.js';
|
|
9
10
|
export class PiperEngine extends TTSEngine {
|
|
10
11
|
constructor() {
|
|
11
12
|
super(...arguments);
|
|
@@ -13,16 +14,15 @@ export class PiperEngine extends TTSEngine {
|
|
|
13
14
|
this.modelPath = '';
|
|
14
15
|
}
|
|
15
16
|
async initialize(config) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
17
|
+
// Use provided config or fall back to environment/auto-detection
|
|
18
|
+
this.piperPath = config.piperPath || getPiperPath();
|
|
19
19
|
if (config.modelPath) {
|
|
20
20
|
this.modelPath = config.modelPath;
|
|
21
21
|
}
|
|
22
22
|
else {
|
|
23
|
-
this.modelPath =
|
|
23
|
+
this.modelPath = getModelPath();
|
|
24
24
|
}
|
|
25
|
-
Logger.debug(`Piper engine initialized with model path: ${this.modelPath}`);
|
|
25
|
+
Logger.debug(`Piper engine initialized with piper path: ${this.piperPath}, model path: ${this.modelPath}`);
|
|
26
26
|
}
|
|
27
27
|
preprocessText(text) {
|
|
28
28
|
// Add line breaks after periods to create longer pauses
|
|
@@ -38,13 +38,20 @@ export class PiperEngine extends TTSEngine {
|
|
|
38
38
|
if (!text || text.trim().length === 0) {
|
|
39
39
|
throw new Error('Text cannot be empty');
|
|
40
40
|
}
|
|
41
|
-
// Build path to model file
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
// Build path to model file using configured model path
|
|
42
|
+
let modelFile;
|
|
43
|
+
if (this.modelPath.endsWith('.onnx')) {
|
|
44
|
+
// If modelPath is already a full model path, use it directly
|
|
45
|
+
modelFile = this.modelPath;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// Otherwise, treat it as a directory and append model filename
|
|
49
|
+
modelFile = join(this.modelPath, `${voiceProfile.piperModel}.onnx`);
|
|
50
|
+
}
|
|
44
51
|
if (!existsSync(modelFile)) {
|
|
45
52
|
throw new Error(`Piper model not found: ${voiceProfile.piperModel}\n` +
|
|
46
53
|
`Expected location: ${modelFile}\n` +
|
|
47
|
-
`Run 'npm run setup' to download models.`);
|
|
54
|
+
`Run 'npm run setup' to download models or set MIA_NARRATIVE_PIPER_MODEL environment variable.`);
|
|
48
55
|
}
|
|
49
56
|
const tempDir = tmpdir();
|
|
50
57
|
const uid = randomUUID();
|
|
@@ -75,7 +82,9 @@ export class PiperEngine extends TTSEngine {
|
|
|
75
82
|
}
|
|
76
83
|
}
|
|
77
84
|
async getVoices() {
|
|
78
|
-
const modelDir =
|
|
85
|
+
const modelDir = this.modelPath.endsWith('.onnx')
|
|
86
|
+
? this.modelPath.substring(0, this.modelPath.lastIndexOf('/'))
|
|
87
|
+
: this.modelPath;
|
|
79
88
|
return Object.values(VOICE_PROFILES)
|
|
80
89
|
.filter(v => {
|
|
81
90
|
const modelFile = join(modelDir, `${v.piperModel}.onnx`);
|
|
@@ -93,7 +102,6 @@ export class PiperEngine extends TTSEngine {
|
|
|
93
102
|
execSync('which piper', { stdio: 'pipe' });
|
|
94
103
|
}
|
|
95
104
|
else {
|
|
96
|
-
const { existsSync } = await import('fs');
|
|
97
105
|
if (!existsSync(this.piperPath)) {
|
|
98
106
|
return false;
|
|
99
107
|
}
|
|
@@ -134,7 +142,7 @@ export class PiperEngine extends TTSEngine {
|
|
|
134
142
|
}
|
|
135
143
|
});
|
|
136
144
|
piper.on('error', (error) => {
|
|
137
|
-
reject(new Error(`Failed to start Piper: ${error.message}
|
|
145
|
+
reject(new Error(`Failed to start Piper: ${error.message}\n\n${getPiperMissingMessage()}`));
|
|
138
146
|
});
|
|
139
147
|
});
|
|
140
148
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"piper.js","sourceRoot":"","sources":["../../../src/engines/piper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AACzE,OAAO,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"piper.js","sourceRoot":"","sources":["../../../src/engines/piper.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-reader.d.ts","sourceRoot":"","sources":["../../../src/utils/file-reader.ts"],"names":[],"mappings":"AAIA,qBAAa,UAAU;IACrB,MAAM,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAY7C,MAAM,CAAC,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAQ5C
|
|
1
|
+
{"version":3,"file":"file-reader.d.ts","sourceRoot":"","sources":["../../../src/utils/file-reader.ts"],"names":[],"mappings":"AAIA,qBAAa,UAAU;IACrB,MAAM,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAY7C,MAAM,CAAC,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAQ5C;;;OAGG;IACH,MAAM,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CA6D3C"}
|
|
@@ -24,9 +24,18 @@ export class FileReader {
|
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
26
|
* Strip Markdown syntax from text, leaving only readable content
|
|
27
|
+
* Also adds strategic pauses for better TTS pacing
|
|
27
28
|
*/
|
|
28
29
|
static stripMarkdown(text) {
|
|
29
30
|
let result = text;
|
|
31
|
+
// Add pauses before headers (before stripping syntax)
|
|
32
|
+
// Insert period+newline twice before headers for longer pause
|
|
33
|
+
result = result.replace(/^(#{1,6})\s+/gm, '\n\n. \n. \n$1 ');
|
|
34
|
+
// Add pause after list items (before stripping list markers)
|
|
35
|
+
result = result.replace(/^([\s]*[-*+]\s+.+)$/gm, '$1. ');
|
|
36
|
+
result = result.replace(/^([\s]*\d+\.\s+.+)$/gm, '$1. ');
|
|
37
|
+
// Add pause after colons (in regular text)
|
|
38
|
+
result = result.replace(/:\s+([^\n])/g, ': . $1');
|
|
30
39
|
// Remove code blocks (``` ... ```)
|
|
31
40
|
result = result.replace(/```[\s\S]*?```/g, '');
|
|
32
41
|
// Remove indented code blocks (4 spaces or tab at start of line)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-reader.js","sourceRoot":"","sources":["../../../src/utils/file-reader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC/B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,MAAM,OAAO,UAAU;IACrB,MAAM,CAAC,YAAY,CAAC,QAAgB;QAClC,IAAI,CAAC;YACH,MAAM,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;YACvC,OAAO,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QAC7C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;gBAC3B,MAAM,CAAC,KAAK,CAAC,wBAAwB,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YACxD,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED,MAAM,CAAC,UAAU,CAAC,QAAgB;QAChC,IAAI,CAAC;YACH,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC;QAC9B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED
|
|
1
|
+
{"version":3,"file":"file-reader.js","sourceRoot":"","sources":["../../../src/utils/file-reader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export interface PrerequisiteStatus {
|
|
2
|
+
piperPath: string;
|
|
3
|
+
piperAvailable: boolean;
|
|
4
|
+
modelPath: string;
|
|
5
|
+
modelAvailable: boolean;
|
|
6
|
+
configPath: string;
|
|
7
|
+
configAvailable: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Get the path to the piper binary
|
|
11
|
+
* Priority: MIA_NARRATIVE_PIPER_PATH env var → which piper → /opt/mianar/bin/piper
|
|
12
|
+
*/
|
|
13
|
+
export declare function getPiperPath(): string;
|
|
14
|
+
/**
|
|
15
|
+
* Get the path to piper model file
|
|
16
|
+
* Priority: MIA_NARRATIVE_PIPER_MODEL env var → /opt/mianar/piper-tts/... → ~/.local/share/piper-tts/...
|
|
17
|
+
*/
|
|
18
|
+
export declare function getModelPath(): string;
|
|
19
|
+
/**
|
|
20
|
+
* Check piper binary availability
|
|
21
|
+
*/
|
|
22
|
+
export declare function isPiperAvailable(): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Check if model is available
|
|
25
|
+
*/
|
|
26
|
+
export declare function isModelAvailable(modelName?: string): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Get comprehensive prerequisite status
|
|
29
|
+
*/
|
|
30
|
+
export declare function getPrerequisiteStatus(): PrerequisiteStatus;
|
|
31
|
+
/**
|
|
32
|
+
* Generate helpful error message when piper is not available
|
|
33
|
+
*/
|
|
34
|
+
export declare function getPiperMissingMessage(): string;
|
|
35
|
+
/**
|
|
36
|
+
* Generate helpful error message when models are not available
|
|
37
|
+
*/
|
|
38
|
+
export declare function getModelsMissingMessage(): string;
|
|
39
|
+
/**
|
|
40
|
+
* Ensure directory exists for models
|
|
41
|
+
*/
|
|
42
|
+
export declare function ensureModelDirectory(): void;
|
|
43
|
+
/**
|
|
44
|
+
* Check if mpg123 (MP3 audio player) is installed
|
|
45
|
+
*/
|
|
46
|
+
export declare function isMpg123Available(): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Generate helpful message about mpg123 installation
|
|
49
|
+
*/
|
|
50
|
+
export declare function getMpg123MissingMessage(): string;
|
|
51
|
+
/**
|
|
52
|
+
* Attempt to install mpg123 automatically (Linux only)
|
|
53
|
+
*/
|
|
54
|
+
export declare function tryInstallMpg123(): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Ensure mpg123 is available, try to install if missing
|
|
57
|
+
*/
|
|
58
|
+
export declare function ensureMpg123(): boolean;
|
|
59
|
+
//# sourceMappingURL=prerequisites.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prerequisites.d.ts","sourceRoot":"","sources":["../../../src/utils/prerequisites.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,OAAO,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,OAAO,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED;;;GAGG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAqBrC;AAED;;;GAGG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAiBrC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAe1C;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,GAAE,MAA8B,GAAG,OAAO,CAMnF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,kBAAkB,CAY1D;AAED;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAoB/C;AAED;;GAEG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAoBhD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAM3C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAO3C;AAED;;GAEG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAYhD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAkB1C;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,OAAO,CAYtC"}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { existsSync, mkdirSync } from 'fs';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { Logger } from './logger.js';
|
|
6
|
+
const OPT_MIANAR_PATH = '/opt/mianar';
|
|
7
|
+
const OPT_MIANAR_PIPER_MODEL = '/opt/mianar/piper-tts/en_US-lessac-medium.onnx';
|
|
8
|
+
/**
|
|
9
|
+
* Get the path to the piper binary
|
|
10
|
+
* Priority: MIA_NARRATIVE_PIPER_PATH env var → which piper → /opt/mianar/bin/piper
|
|
11
|
+
*/
|
|
12
|
+
export function getPiperPath() {
|
|
13
|
+
// 1. Check explicit environment variable
|
|
14
|
+
if (process.env.MIA_NARRATIVE_PIPER_PATH) {
|
|
15
|
+
Logger.debug(`Using MIA_NARRATIVE_PIPER_PATH: ${process.env.MIA_NARRATIVE_PIPER_PATH}`);
|
|
16
|
+
return process.env.MIA_NARRATIVE_PIPER_PATH;
|
|
17
|
+
}
|
|
18
|
+
// 2. Try to find piper in PATH
|
|
19
|
+
try {
|
|
20
|
+
const result = execSync('which piper', { stdio: 'pipe' }).toString().trim();
|
|
21
|
+
if (result) {
|
|
22
|
+
Logger.debug(`Found piper in PATH: ${result}`);
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// Not in PATH
|
|
28
|
+
}
|
|
29
|
+
// 3. Default fallback
|
|
30
|
+
Logger.debug('Using default piper path: piper (will try to find in PATH at runtime)');
|
|
31
|
+
return 'piper';
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Get the path to piper model file
|
|
35
|
+
* Priority: MIA_NARRATIVE_PIPER_MODEL env var → /opt/mianar/piper-tts/... → ~/.local/share/piper-tts/...
|
|
36
|
+
*/
|
|
37
|
+
export function getModelPath() {
|
|
38
|
+
// 1. Check explicit environment variable
|
|
39
|
+
if (process.env.MIA_NARRATIVE_PIPER_MODEL) {
|
|
40
|
+
Logger.debug(`Using MIA_NARRATIVE_PIPER_MODEL: ${process.env.MIA_NARRATIVE_PIPER_MODEL}`);
|
|
41
|
+
return process.env.MIA_NARRATIVE_PIPER_MODEL;
|
|
42
|
+
}
|
|
43
|
+
// 2. Check /opt/mianar structure
|
|
44
|
+
if (existsSync(OPT_MIANAR_PIPER_MODEL)) {
|
|
45
|
+
Logger.debug(`Found model in /opt/mianar: ${OPT_MIANAR_PIPER_MODEL}`);
|
|
46
|
+
return OPT_MIANAR_PIPER_MODEL;
|
|
47
|
+
}
|
|
48
|
+
// 3. Default to home directory structure
|
|
49
|
+
const defaultPath = join(homedir(), '.local/share/piper-tts');
|
|
50
|
+
Logger.debug(`Using default model directory: ${defaultPath}`);
|
|
51
|
+
return defaultPath;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Check piper binary availability
|
|
55
|
+
*/
|
|
56
|
+
export function isPiperAvailable() {
|
|
57
|
+
try {
|
|
58
|
+
const piperPath = getPiperPath();
|
|
59
|
+
if (piperPath === 'piper') {
|
|
60
|
+
// Try to find piper in PATH
|
|
61
|
+
execSync('which piper > /dev/null 2>&1');
|
|
62
|
+
}
|
|
63
|
+
else if (existsSync(piperPath)) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Check if model is available
|
|
77
|
+
*/
|
|
78
|
+
export function isModelAvailable(modelName = 'en_US-lessac-medium') {
|
|
79
|
+
const modelPath = getModelPath();
|
|
80
|
+
const modelFile = join(modelPath, `${modelName}.onnx`);
|
|
81
|
+
const configFile = `${modelFile}.json`;
|
|
82
|
+
return existsSync(modelFile) && existsSync(configFile);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Get comprehensive prerequisite status
|
|
86
|
+
*/
|
|
87
|
+
export function getPrerequisiteStatus() {
|
|
88
|
+
const piperPath = getPiperPath();
|
|
89
|
+
const modelPath = getModelPath();
|
|
90
|
+
return {
|
|
91
|
+
piperPath,
|
|
92
|
+
piperAvailable: isPiperAvailable(),
|
|
93
|
+
modelPath,
|
|
94
|
+
modelAvailable: isModelAvailable(),
|
|
95
|
+
configPath: `${modelPath}/en_US-lessac-medium.onnx.json`,
|
|
96
|
+
configAvailable: existsSync(join(modelPath, 'en_US-lessac-medium.onnx.json')),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Generate helpful error message when piper is not available
|
|
101
|
+
*/
|
|
102
|
+
export function getPiperMissingMessage() {
|
|
103
|
+
const status = getPrerequisiteStatus();
|
|
104
|
+
return `
|
|
105
|
+
❌ Piper TTS is not available
|
|
106
|
+
|
|
107
|
+
Piper path checked: ${status.piperPath}
|
|
108
|
+
Piper available: ${status.piperAvailable ? '✓' : '✗'}
|
|
109
|
+
|
|
110
|
+
To install Piper:
|
|
111
|
+
1. Using pip:
|
|
112
|
+
pip install -U piper-tts
|
|
113
|
+
|
|
114
|
+
2. Then ensure it's in your PATH or set MIA_NARRATIVE_PIPER_PATH
|
|
115
|
+
|
|
116
|
+
3. Or use the setup script:
|
|
117
|
+
npm run setup
|
|
118
|
+
|
|
119
|
+
For more info: https://github.com/rhasspy/piper
|
|
120
|
+
`;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Generate helpful error message when models are not available
|
|
124
|
+
*/
|
|
125
|
+
export function getModelsMissingMessage() {
|
|
126
|
+
const status = getPrerequisiteStatus();
|
|
127
|
+
return `
|
|
128
|
+
❌ Piper TTS models are not available
|
|
129
|
+
|
|
130
|
+
Looking in: ${status.modelPath}
|
|
131
|
+
Models available: ${status.modelAvailable ? '✓' : '✗'}
|
|
132
|
+
|
|
133
|
+
To download models:
|
|
134
|
+
1. Run the setup script:
|
|
135
|
+
npm run setup
|
|
136
|
+
|
|
137
|
+
2. Or manually download to:
|
|
138
|
+
${status.modelPath}
|
|
139
|
+
|
|
140
|
+
Expected file:
|
|
141
|
+
en_US-lessac-medium.onnx
|
|
142
|
+
en_US-lessac-medium.onnx.json
|
|
143
|
+
`;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Ensure directory exists for models
|
|
147
|
+
*/
|
|
148
|
+
export function ensureModelDirectory() {
|
|
149
|
+
const modelPath = getModelPath();
|
|
150
|
+
if (!existsSync(modelPath)) {
|
|
151
|
+
mkdirSync(modelPath, { recursive: true });
|
|
152
|
+
Logger.debug(`Created model directory: ${modelPath}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Check if mpg123 (MP3 audio player) is installed
|
|
157
|
+
*/
|
|
158
|
+
export function isMpg123Available() {
|
|
159
|
+
try {
|
|
160
|
+
execSync('which mpg123 > /dev/null 2>&1');
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Generate helpful message about mpg123 installation
|
|
169
|
+
*/
|
|
170
|
+
export function getMpg123MissingMessage() {
|
|
171
|
+
return `
|
|
172
|
+
⚠️ MP3 player (mpg123) not found
|
|
173
|
+
|
|
174
|
+
To play generated audio files, install mpg123:
|
|
175
|
+
sudo apt-get install mpg123 -y (Linux)
|
|
176
|
+
brew install mpg123 (macOS)
|
|
177
|
+
|
|
178
|
+
Or manually download from: https://www.mpg123.de/
|
|
179
|
+
|
|
180
|
+
After installation, audio playback will work automatically.
|
|
181
|
+
`;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Attempt to install mpg123 automatically (Linux only)
|
|
185
|
+
*/
|
|
186
|
+
export function tryInstallMpg123() {
|
|
187
|
+
try {
|
|
188
|
+
const { platform } = require('os');
|
|
189
|
+
// Only try on Linux systems with apt
|
|
190
|
+
if (platform() !== 'linux') {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
execSync('which apt-get > /dev/null 2>&1');
|
|
194
|
+
Logger.debug('Attempting to install mpg123...');
|
|
195
|
+
execSync('sudo apt-get install mpg123 -y', { stdio: 'pipe' });
|
|
196
|
+
Logger.debug('✓ mpg123 installed successfully');
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
Logger.debug(`Failed to auto-install mpg123: ${error}`);
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Ensure mpg123 is available, try to install if missing
|
|
206
|
+
*/
|
|
207
|
+
export function ensureMpg123() {
|
|
208
|
+
if (isMpg123Available()) {
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
// Try to install
|
|
212
|
+
if (tryInstallMpg123()) {
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
Logger.warn(getMpg123MissingMessage());
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=prerequisites.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prerequisites.js","sourceRoot":"","sources":["../../../src/utils/prerequisites.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
fread() # Read a file with --file instead of --text (default: Mia voice)
|
|
2
|
+
{
|
|
3
|
+
local _file="$1"
|
|
4
|
+
if [ -z "$_file" ];then
|
|
5
|
+
echo "Usage: fread <file>"
|
|
6
|
+
return 1
|
|
7
|
+
fi
|
|
8
|
+
local _withou_extension=$(basename "$_file" | cut -f 1 -d '.')
|
|
9
|
+
#the output file is the same as the input file but .mp3 and is NOT in /tmp
|
|
10
|
+
local _output_file="$_withou_extension.mp3"
|
|
11
|
+
|
|
12
|
+
mia-narrative generate --engine piper --file "$_file" --output $_output_file || {
|
|
13
|
+
echo "Failed to generate audio with mia-narrative."
|
|
14
|
+
return 1
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
# Try to play audio, but don't fail if unavailable (e.g., headless/SSH without forwarding)
|
|
18
|
+
if mpg123 $_output_file 2>/dev/null; then
|
|
19
|
+
: # Audio played successfully
|
|
20
|
+
else
|
|
21
|
+
echo "✓ Generated: $_output_file (audio playback unavailable - use audio-gaia/audio-hu to enable)"
|
|
22
|
+
fi
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# Persona-specific TTS functions
|
|
26
|
+
fread_mia() { # Professional female narrator
|
|
27
|
+
local _file="$1"
|
|
28
|
+
[ -z "$_file" ] && echo "Usage: fread_mia <file>" && return 1
|
|
29
|
+
local _output="$(basename "$_file" .txt).mp3"
|
|
30
|
+
mia-narrative generate --engine piper --voiceId mia --file "$_file" --output "$_output" || return 1
|
|
31
|
+
mpg123 "$_output" 2>/dev/null || echo "✓ Generated: $_output (audio playback unavailable)"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
fread_miette() { # Conversational female narrator
|
|
35
|
+
local _file="$1"
|
|
36
|
+
[ -z "$_file" ] && echo "Usage: fread_miette <file>" && return 1
|
|
37
|
+
local _output="$(basename "$_file" .txt).mp3"
|
|
38
|
+
mia-narrative generate --engine piper --voiceId miette --file "$_file" --output "$_output" || return 1
|
|
39
|
+
mpg123 "$_output" 2>/dev/null || echo "✓ Generated: $_output (audio playback unavailable)"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
fread_seraphine() { # Expressive narrative female voice
|
|
43
|
+
local _file="$1"
|
|
44
|
+
[ -z "$_file" ] && echo "Usage: fread_seraphine <file>" && return 1
|
|
45
|
+
local _output="$(basename "$_file" .txt).mp3"
|
|
46
|
+
mia-narrative generate --engine piper --voiceId seraphine --file "$_file" --output "$_output" || return 1
|
|
47
|
+
mpg123 "$_output" 2>/dev/null || echo "✓ Generated: $_output (audio playback unavailable)"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
fread_jeremy() { # Professional male narrator
|
|
51
|
+
local _file="$1"
|
|
52
|
+
[ -z "$_file" ] && echo "Usage: fread_jeremy <file>" && return 1
|
|
53
|
+
local _output="$(basename "$_file" .txt).mp3"
|
|
54
|
+
mia-narrative generate --engine piper --voiceId jeremy --file "$_file" --output "$_output" || return 1
|
|
55
|
+
mpg123 "$_output" 2>/dev/null || echo "✓ Generated: $_output (audio playback unavailable)"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
fread_atlas() { # Casual male voice
|
|
59
|
+
local _file="$1"
|
|
60
|
+
[ -z "$_file" ] && echo "Usage: fread_atlas <file>" && return 1
|
|
61
|
+
local _output="$(basename "$_file" .txt).mp3"
|
|
62
|
+
mia-narrative generate --engine piper --voiceId atlas --file "$_file" --output "$_output" || return 1
|
|
63
|
+
mpg123 "$_output" 2>/dev/null || echo "✓ Generated: $_output (audio playback unavailable)"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
fread_resonova() { # Expressive female voice
|
|
67
|
+
local _file="$1"
|
|
68
|
+
[ -z "$_file" ] && echo "Usage: fread_resonova <file>" && return 1
|
|
69
|
+
local _output="$(basename "$_file" .txt).mp3"
|
|
70
|
+
mia-narrative generate --engine piper --voiceId resonova --file "$_file" --output "$_output" || return 1
|
|
71
|
+
mpg123 "$_output" 2>/dev/null || echo "✓ Generated: $_output (audio playback unavailable)"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
fread_zephyr() { # Neutral contemplative voice
|
|
75
|
+
local _file="$1"
|
|
76
|
+
[ -z "$_file" ] && echo "Usage: fread_zephyr <file>" && return 1
|
|
77
|
+
local _output="$(basename "$_file" .txt).mp3"
|
|
78
|
+
mia-narrative generate --engine piper --voiceId zephyr --file "$_file" --output "$_output" || return 1
|
|
79
|
+
mpg123 "$_output" 2>/dev/null || echo "✓ Generated: $_output (audio playback unavailable)"
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
fread_echo() { # Expressive character voice
|
|
83
|
+
local _file="$1"
|
|
84
|
+
[ -z "$_file" ] && echo "Usage: fread_echo <file>" && return 1
|
|
85
|
+
local _output="$(basename "$_file" .txt).mp3"
|
|
86
|
+
mia-narrative generate --engine piper --voiceId echo --file "$_file" --output "$_output" || return 1
|
|
87
|
+
mpg123 "$_output" 2>/dev/null || echo "✓ Generated: $_output (audio playback unavailable)"
|
|
88
|
+
}
|
|
89
|
+
|
package/scripts/setup.js
CHANGED
|
@@ -7,6 +7,8 @@ import { join } from 'path';
|
|
|
7
7
|
|
|
8
8
|
const HOME = homedir();
|
|
9
9
|
const PIPER_MODEL_DIR = join(HOME, '.local/share/piper-tts');
|
|
10
|
+
const OPT_MIANAR_PATH = '/opt/mianar';
|
|
11
|
+
const DROPBOX_URL = 'https://www.dropbox.com/scl/fi/vl4mmi4wpz0eqtjljdh6c/opt_mianar_prerequisite_piper.tar?rlkey=paq9nfeoppfzjua1whsu7476z&dl=1';
|
|
10
12
|
|
|
11
13
|
// Voice models to download (maps to voice profiles)
|
|
12
14
|
const MODELS = [
|
|
@@ -20,30 +22,53 @@ const MODELS = [
|
|
|
20
22
|
|
|
21
23
|
console.log('\n🎙️ mia-narrative Setup\n');
|
|
22
24
|
|
|
25
|
+
// Check if /opt/mianar prerequisites are already downloaded
|
|
26
|
+
console.log('1️⃣ Checking for /opt/mianar prerequisites...');
|
|
27
|
+
if (existsSync(OPT_MIANAR_PATH)) {
|
|
28
|
+
const piperTtsPath = join(OPT_MIANAR_PATH, 'piper-tts');
|
|
29
|
+
if (existsSync(piperTtsPath)) {
|
|
30
|
+
console.log(' ✓ Found /opt/mianar/piper-tts');
|
|
31
|
+
console.log(` → Using: ${piperTtsPath}`);
|
|
32
|
+
process.env.MIA_NARRATIVE_PIPER_MODEL = join(piperTtsPath, 'en_US-lessac-medium.onnx');
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
console.log(' ⚠️ /opt/mianar not found, will download to home directory');
|
|
36
|
+
}
|
|
37
|
+
|
|
23
38
|
// Check Piper installation
|
|
24
|
-
console.log('
|
|
39
|
+
console.log('2️⃣ Checking for Piper TTS...');
|
|
25
40
|
try {
|
|
26
41
|
execSync('which piper > /dev/null 2>&1');
|
|
27
42
|
console.log(' ✓ Piper is installed');
|
|
28
43
|
} catch {
|
|
29
44
|
console.log(' ⚠️ Piper not found in PATH');
|
|
30
|
-
console.log(' → Install from:
|
|
31
|
-
|
|
45
|
+
console.log(' → Install from: pip install -U piper-tts');
|
|
46
|
+
console.log(' → After install, make sure "piper" is in your PATH');
|
|
32
47
|
}
|
|
33
48
|
|
|
34
49
|
// Check FFmpeg
|
|
35
|
-
console.log('
|
|
50
|
+
console.log('3️⃣ Checking for FFmpeg...');
|
|
36
51
|
try {
|
|
37
52
|
execSync('which ffmpeg > /dev/null 2>&1');
|
|
38
53
|
console.log(' ✓ FFmpeg is installed');
|
|
39
54
|
} catch {
|
|
40
55
|
console.log(' ⚠️ FFmpeg not found');
|
|
41
56
|
console.log(' → Install: brew install ffmpeg (macOS) or apt-get install ffmpeg (Linux)');
|
|
42
|
-
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check mpg123
|
|
60
|
+
console.log('4️⃣ Checking for MP3 player (mpg123)...');
|
|
61
|
+
try {
|
|
62
|
+
execSync('which mpg123 > /dev/null 2>&1');
|
|
63
|
+
console.log(' ✓ mpg123 is installed');
|
|
64
|
+
} catch {
|
|
65
|
+
console.log(' ⚠️ mpg123 not found');
|
|
66
|
+
console.log(' → For MP3 playback, install: sudo apt-get install mpg123 -y (Linux)');
|
|
67
|
+
console.log(' → Or: brew install mpg123 (macOS)');
|
|
43
68
|
}
|
|
44
69
|
|
|
45
70
|
// Check/Setup Piper models
|
|
46
|
-
console.log(`
|
|
71
|
+
console.log(`5️⃣ Setting up Piper voice models...`);
|
|
47
72
|
mkdirSync(PIPER_MODEL_DIR, { recursive: true });
|
|
48
73
|
|
|
49
74
|
const totalSize = MODELS.reduce((sum, m) => {
|
|
@@ -97,10 +122,18 @@ for (const model of MODELS) {
|
|
|
97
122
|
console.log('');
|
|
98
123
|
if (downloadedCount === 0) {
|
|
99
124
|
console.log(' ⚠️ No models downloaded. Check your internet connection.');
|
|
100
|
-
|
|
125
|
+
} else {
|
|
126
|
+
console.log(` ✓ Downloaded ${downloadedCount}/${MODELS.length} models`);
|
|
101
127
|
}
|
|
102
128
|
|
|
103
|
-
|
|
129
|
+
// Suggest /opt/mianar download
|
|
130
|
+
console.log('\n6️⃣ Optional: Download /opt/mianar prerequisites...');
|
|
131
|
+
if (!existsSync(OPT_MIANAR_PATH)) {
|
|
132
|
+
console.log(' → To use /opt/mianar structure:');
|
|
133
|
+
console.log(' → Run: mkdir -p /opt/mianar && sudo chown $USER /opt/mianar');
|
|
134
|
+
console.log(' → Then: wget -q -O - ' + DROPBOX_URL + ' | tar -xz -C /opt/mianar');
|
|
135
|
+
console.log(' → Set: export MIA_NARRATIVE_PIPER_MODEL=/opt/mianar/piper-tts/en_US-lessac-medium.onnx');
|
|
136
|
+
}
|
|
104
137
|
|
|
105
138
|
console.log('\n✅ Setup complete!');
|
|
106
139
|
console.log('\nModels installed to:', PIPER_MODEL_DIR);
|
|
@@ -108,4 +141,7 @@ console.log('\nNext steps:');
|
|
|
108
141
|
console.log('1. Test: mia-narrative generate --engine piper --text "hello"');
|
|
109
142
|
console.log('2. List voices: mia-narrative voices --engine piper');
|
|
110
143
|
console.log('3. Try a specific voice: mia-narrative generate --engine piper --voice miette-default --text "hello"');
|
|
144
|
+
console.log('\nEnvironment variables you can set:');
|
|
145
|
+
console.log(' MIA_NARRATIVE_PIPER_PATH=/path/to/piper');
|
|
146
|
+
console.log(' MIA_NARRATIVE_PIPER_MODEL=/path/to/model.onnx or /path/to/models/directory');
|
|
111
147
|
console.log('\n');
|
package/src/engines/piper.ts
CHANGED
|
@@ -6,23 +6,23 @@ import { randomUUID } from 'crypto';
|
|
|
6
6
|
import { Logger } from '../utils/logger.js';
|
|
7
7
|
import { TTSEngine, TTSEngineConfig, GenerateAudioOptions, Voice } from './base.js';
|
|
8
8
|
import { getVoiceProfile, VOICE_PROFILES } from '../config/voices.js';
|
|
9
|
+
import { getPiperPath, getModelPath, getPiperMissingMessage } from '../utils/prerequisites.js';
|
|
9
10
|
|
|
10
11
|
export class PiperEngine extends TTSEngine {
|
|
11
12
|
private piperPath: string = 'piper';
|
|
12
13
|
private modelPath: string = '';
|
|
13
14
|
|
|
14
15
|
async initialize(config: TTSEngineConfig): Promise<void> {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
16
|
+
// Use provided config or fall back to environment/auto-detection
|
|
17
|
+
this.piperPath = config.piperPath || getPiperPath();
|
|
18
18
|
|
|
19
19
|
if (config.modelPath) {
|
|
20
20
|
this.modelPath = config.modelPath;
|
|
21
21
|
} else {
|
|
22
|
-
this.modelPath =
|
|
22
|
+
this.modelPath = getModelPath();
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
Logger.debug(`Piper engine initialized with model path: ${this.modelPath}`);
|
|
25
|
+
Logger.debug(`Piper engine initialized with piper path: ${this.piperPath}, model path: ${this.modelPath}`);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
private preprocessText(text: string): string {
|
|
@@ -43,15 +43,21 @@ export class PiperEngine extends TTSEngine {
|
|
|
43
43
|
throw new Error('Text cannot be empty');
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
// Build path to model file
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
// Build path to model file using configured model path
|
|
47
|
+
let modelFile: string;
|
|
48
|
+
if (this.modelPath.endsWith('.onnx')) {
|
|
49
|
+
// If modelPath is already a full model path, use it directly
|
|
50
|
+
modelFile = this.modelPath;
|
|
51
|
+
} else {
|
|
52
|
+
// Otherwise, treat it as a directory and append model filename
|
|
53
|
+
modelFile = join(this.modelPath, `${voiceProfile.piperModel}.onnx`);
|
|
54
|
+
}
|
|
49
55
|
|
|
50
56
|
if (!existsSync(modelFile)) {
|
|
51
57
|
throw new Error(
|
|
52
58
|
`Piper model not found: ${voiceProfile.piperModel}\n` +
|
|
53
59
|
`Expected location: ${modelFile}\n` +
|
|
54
|
-
`Run 'npm run setup' to download models.`
|
|
60
|
+
`Run 'npm run setup' to download models or set MIA_NARRATIVE_PIPER_MODEL environment variable.`
|
|
55
61
|
);
|
|
56
62
|
}
|
|
57
63
|
|
|
@@ -83,7 +89,9 @@ export class PiperEngine extends TTSEngine {
|
|
|
83
89
|
}
|
|
84
90
|
|
|
85
91
|
async getVoices(): Promise<Voice[]> {
|
|
86
|
-
const modelDir =
|
|
92
|
+
const modelDir = this.modelPath.endsWith('.onnx')
|
|
93
|
+
? this.modelPath.substring(0, this.modelPath.lastIndexOf('/'))
|
|
94
|
+
: this.modelPath;
|
|
87
95
|
|
|
88
96
|
return Object.values(VOICE_PROFILES)
|
|
89
97
|
.filter(v => {
|
|
@@ -102,7 +110,6 @@ export class PiperEngine extends TTSEngine {
|
|
|
102
110
|
if (this.piperPath === 'piper') {
|
|
103
111
|
execSync('which piper', { stdio: 'pipe' });
|
|
104
112
|
} else {
|
|
105
|
-
const { existsSync } = await import('fs');
|
|
106
113
|
if (!existsSync(this.piperPath)) {
|
|
107
114
|
return false;
|
|
108
115
|
}
|
|
@@ -155,7 +162,7 @@ export class PiperEngine extends TTSEngine {
|
|
|
155
162
|
piper.on('error', (error) => {
|
|
156
163
|
reject(
|
|
157
164
|
new Error(
|
|
158
|
-
`Failed to start Piper: ${error.message}
|
|
165
|
+
`Failed to start Piper: ${error.message}\n\n${getPiperMissingMessage()}`
|
|
159
166
|
)
|
|
160
167
|
);
|
|
161
168
|
});
|
package/src/utils/file-reader.ts
CHANGED
|
@@ -25,10 +25,22 @@ export class FileReader {
|
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Strip Markdown syntax from text, leaving only readable content
|
|
28
|
+
* Also adds strategic pauses for better TTS pacing
|
|
28
29
|
*/
|
|
29
30
|
static stripMarkdown(text: string): string {
|
|
30
31
|
let result = text;
|
|
31
32
|
|
|
33
|
+
// Add pauses before headers (before stripping syntax)
|
|
34
|
+
// Insert period+newline twice before headers for longer pause
|
|
35
|
+
result = result.replace(/^(#{1,6})\s+/gm, '\n\n. \n. \n$1 ');
|
|
36
|
+
|
|
37
|
+
// Add pause after list items (before stripping list markers)
|
|
38
|
+
result = result.replace(/^([\s]*[-*+]\s+.+)$/gm, '$1. ');
|
|
39
|
+
result = result.replace(/^([\s]*\d+\.\s+.+)$/gm, '$1. ');
|
|
40
|
+
|
|
41
|
+
// Add pause after colons (in regular text)
|
|
42
|
+
result = result.replace(/:\s+([^\n])/g, ': . $1');
|
|
43
|
+
|
|
32
44
|
// Remove code blocks (``` ... ```)
|
|
33
45
|
result = result.replace(/```[\s\S]*?```/g, '');
|
|
34
46
|
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { existsSync, mkdirSync } from 'fs';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { Logger } from './logger.js';
|
|
6
|
+
|
|
7
|
+
const OPT_MIANAR_PATH = '/opt/mianar';
|
|
8
|
+
const OPT_MIANAR_PIPER_MODEL = '/opt/mianar/piper-tts/en_US-lessac-medium.onnx';
|
|
9
|
+
|
|
10
|
+
export interface PrerequisiteStatus {
|
|
11
|
+
piperPath: string;
|
|
12
|
+
piperAvailable: boolean;
|
|
13
|
+
modelPath: string;
|
|
14
|
+
modelAvailable: boolean;
|
|
15
|
+
configPath: string;
|
|
16
|
+
configAvailable: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get the path to the piper binary
|
|
21
|
+
* Priority: MIA_NARRATIVE_PIPER_PATH env var → which piper → /opt/mianar/bin/piper
|
|
22
|
+
*/
|
|
23
|
+
export function getPiperPath(): string {
|
|
24
|
+
// 1. Check explicit environment variable
|
|
25
|
+
if (process.env.MIA_NARRATIVE_PIPER_PATH) {
|
|
26
|
+
Logger.debug(`Using MIA_NARRATIVE_PIPER_PATH: ${process.env.MIA_NARRATIVE_PIPER_PATH}`);
|
|
27
|
+
return process.env.MIA_NARRATIVE_PIPER_PATH;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 2. Try to find piper in PATH
|
|
31
|
+
try {
|
|
32
|
+
const result = execSync('which piper', { stdio: 'pipe' }).toString().trim();
|
|
33
|
+
if (result) {
|
|
34
|
+
Logger.debug(`Found piper in PATH: ${result}`);
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
} catch {
|
|
38
|
+
// Not in PATH
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 3. Default fallback
|
|
42
|
+
Logger.debug('Using default piper path: piper (will try to find in PATH at runtime)');
|
|
43
|
+
return 'piper';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get the path to piper model file
|
|
48
|
+
* Priority: MIA_NARRATIVE_PIPER_MODEL env var → /opt/mianar/piper-tts/... → ~/.local/share/piper-tts/...
|
|
49
|
+
*/
|
|
50
|
+
export function getModelPath(): string {
|
|
51
|
+
// 1. Check explicit environment variable
|
|
52
|
+
if (process.env.MIA_NARRATIVE_PIPER_MODEL) {
|
|
53
|
+
Logger.debug(`Using MIA_NARRATIVE_PIPER_MODEL: ${process.env.MIA_NARRATIVE_PIPER_MODEL}`);
|
|
54
|
+
return process.env.MIA_NARRATIVE_PIPER_MODEL;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 2. Check /opt/mianar structure
|
|
58
|
+
if (existsSync(OPT_MIANAR_PIPER_MODEL)) {
|
|
59
|
+
Logger.debug(`Found model in /opt/mianar: ${OPT_MIANAR_PIPER_MODEL}`);
|
|
60
|
+
return OPT_MIANAR_PIPER_MODEL;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 3. Default to home directory structure
|
|
64
|
+
const defaultPath = join(homedir(), '.local/share/piper-tts');
|
|
65
|
+
Logger.debug(`Using default model directory: ${defaultPath}`);
|
|
66
|
+
return defaultPath;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Check piper binary availability
|
|
71
|
+
*/
|
|
72
|
+
export function isPiperAvailable(): boolean {
|
|
73
|
+
try {
|
|
74
|
+
const piperPath = getPiperPath();
|
|
75
|
+
if (piperPath === 'piper') {
|
|
76
|
+
// Try to find piper in PATH
|
|
77
|
+
execSync('which piper > /dev/null 2>&1');
|
|
78
|
+
} else if (existsSync(piperPath)) {
|
|
79
|
+
return true;
|
|
80
|
+
} else {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
return true;
|
|
84
|
+
} catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Check if model is available
|
|
91
|
+
*/
|
|
92
|
+
export function isModelAvailable(modelName: string = 'en_US-lessac-medium'): boolean {
|
|
93
|
+
const modelPath = getModelPath();
|
|
94
|
+
const modelFile = join(modelPath, `${modelName}.onnx`);
|
|
95
|
+
const configFile = `${modelFile}.json`;
|
|
96
|
+
|
|
97
|
+
return existsSync(modelFile) && existsSync(configFile);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Get comprehensive prerequisite status
|
|
102
|
+
*/
|
|
103
|
+
export function getPrerequisiteStatus(): PrerequisiteStatus {
|
|
104
|
+
const piperPath = getPiperPath();
|
|
105
|
+
const modelPath = getModelPath();
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
piperPath,
|
|
109
|
+
piperAvailable: isPiperAvailable(),
|
|
110
|
+
modelPath,
|
|
111
|
+
modelAvailable: isModelAvailable(),
|
|
112
|
+
configPath: `${modelPath}/en_US-lessac-medium.onnx.json`,
|
|
113
|
+
configAvailable: existsSync(join(modelPath, 'en_US-lessac-medium.onnx.json')),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Generate helpful error message when piper is not available
|
|
119
|
+
*/
|
|
120
|
+
export function getPiperMissingMessage(): string {
|
|
121
|
+
const status = getPrerequisiteStatus();
|
|
122
|
+
|
|
123
|
+
return `
|
|
124
|
+
❌ Piper TTS is not available
|
|
125
|
+
|
|
126
|
+
Piper path checked: ${status.piperPath}
|
|
127
|
+
Piper available: ${status.piperAvailable ? '✓' : '✗'}
|
|
128
|
+
|
|
129
|
+
To install Piper:
|
|
130
|
+
1. Using pip:
|
|
131
|
+
pip install -U piper-tts
|
|
132
|
+
|
|
133
|
+
2. Then ensure it's in your PATH or set MIA_NARRATIVE_PIPER_PATH
|
|
134
|
+
|
|
135
|
+
3. Or use the setup script:
|
|
136
|
+
npm run setup
|
|
137
|
+
|
|
138
|
+
For more info: https://github.com/rhasspy/piper
|
|
139
|
+
`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Generate helpful error message when models are not available
|
|
144
|
+
*/
|
|
145
|
+
export function getModelsMissingMessage(): string {
|
|
146
|
+
const status = getPrerequisiteStatus();
|
|
147
|
+
|
|
148
|
+
return `
|
|
149
|
+
❌ Piper TTS models are not available
|
|
150
|
+
|
|
151
|
+
Looking in: ${status.modelPath}
|
|
152
|
+
Models available: ${status.modelAvailable ? '✓' : '✗'}
|
|
153
|
+
|
|
154
|
+
To download models:
|
|
155
|
+
1. Run the setup script:
|
|
156
|
+
npm run setup
|
|
157
|
+
|
|
158
|
+
2. Or manually download to:
|
|
159
|
+
${status.modelPath}
|
|
160
|
+
|
|
161
|
+
Expected file:
|
|
162
|
+
en_US-lessac-medium.onnx
|
|
163
|
+
en_US-lessac-medium.onnx.json
|
|
164
|
+
`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Ensure directory exists for models
|
|
169
|
+
*/
|
|
170
|
+
export function ensureModelDirectory(): void {
|
|
171
|
+
const modelPath = getModelPath();
|
|
172
|
+
if (!existsSync(modelPath)) {
|
|
173
|
+
mkdirSync(modelPath, { recursive: true });
|
|
174
|
+
Logger.debug(`Created model directory: ${modelPath}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Check if mpg123 (MP3 audio player) is installed
|
|
180
|
+
*/
|
|
181
|
+
export function isMpg123Available(): boolean {
|
|
182
|
+
try {
|
|
183
|
+
execSync('which mpg123 > /dev/null 2>&1');
|
|
184
|
+
return true;
|
|
185
|
+
} catch {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Generate helpful message about mpg123 installation
|
|
192
|
+
*/
|
|
193
|
+
export function getMpg123MissingMessage(): string {
|
|
194
|
+
return `
|
|
195
|
+
⚠️ MP3 player (mpg123) not found
|
|
196
|
+
|
|
197
|
+
To play generated audio files, install mpg123:
|
|
198
|
+
sudo apt-get install mpg123 -y (Linux)
|
|
199
|
+
brew install mpg123 (macOS)
|
|
200
|
+
|
|
201
|
+
Or manually download from: https://www.mpg123.de/
|
|
202
|
+
|
|
203
|
+
After installation, audio playback will work automatically.
|
|
204
|
+
`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Attempt to install mpg123 automatically (Linux only)
|
|
209
|
+
*/
|
|
210
|
+
export function tryInstallMpg123(): boolean {
|
|
211
|
+
try {
|
|
212
|
+
const { platform } = require('os');
|
|
213
|
+
// Only try on Linux systems with apt
|
|
214
|
+
if (platform() !== 'linux') {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
execSync('which apt-get > /dev/null 2>&1');
|
|
219
|
+
|
|
220
|
+
Logger.debug('Attempting to install mpg123...');
|
|
221
|
+
execSync('sudo apt-get install mpg123 -y', { stdio: 'pipe' });
|
|
222
|
+
Logger.debug('✓ mpg123 installed successfully');
|
|
223
|
+
return true;
|
|
224
|
+
} catch (error) {
|
|
225
|
+
Logger.debug(`Failed to auto-install mpg123: ${error}`);
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Ensure mpg123 is available, try to install if missing
|
|
232
|
+
*/
|
|
233
|
+
export function ensureMpg123(): boolean {
|
|
234
|
+
if (isMpg123Available()) {
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Try to install
|
|
239
|
+
if (tryInstallMpg123()) {
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
Logger.warn(getMpg123MissingMessage());
|
|
244
|
+
return false;
|
|
245
|
+
}
|