@oxiaom/adoremix 1.0.9 → 1.0.10
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/package.json +1 -1
- package/src/doctor.js +25 -0
- package/src/tts-deps.js +253 -0
- package/tts/tts.js +5 -3
package/package.json
CHANGED
package/src/doctor.js
CHANGED
|
@@ -162,6 +162,31 @@ function runDoctor(workdir, opts) {
|
|
|
162
162
|
logger.log(`○ 服务未运行(adoremix start 启动)`);
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
// 8. TTS provider 依赖检查
|
|
166
|
+
logger.log('');
|
|
167
|
+
logger.log('=== TTS Provider 检查 ===');
|
|
168
|
+
const cfg = require('./config');
|
|
169
|
+
const cfgObj = cfg.readConfig(workdir) || {};
|
|
170
|
+
const ttsDeps = require('./tts-deps');
|
|
171
|
+
const providerName = (cfgObj.TTS && cfgObj.TTS.provider) || 'xf';
|
|
172
|
+
logger.log(`当前 provider: ${providerName}`);
|
|
173
|
+
const ttsResult = ttsDeps.checkDeps(workdir, providerName, cfgObj);
|
|
174
|
+
if (ttsResult.issues.length === 0) {
|
|
175
|
+
logger.ok(`✓ ${providerName} provider 所有依赖就绪`);
|
|
176
|
+
} else {
|
|
177
|
+
for (const iss of ttsResult.issues) {
|
|
178
|
+
const mark = iss.severity === 'error' ? '❌' : '⚠';
|
|
179
|
+
logger.log(` ${mark} ${iss.msg}`);
|
|
180
|
+
if (iss.fixCmd) logger.log(` 修复:${iss.fixCmd}`);
|
|
181
|
+
if (iss.autoFixable) issues.push({ type: 'tts', severity: iss.severity, msg: iss.msg, fixCmd: iss.fixCmd });
|
|
182
|
+
}
|
|
183
|
+
if (opts.fix) {
|
|
184
|
+
logger.info('==> 自动修复 TTS 依赖...');
|
|
185
|
+
const r = ttsDeps.fixDeps(workdir, ttsResult.issues, { sudo: process.getuid && process.getuid() === 0 });
|
|
186
|
+
logger.ok(`✓ 修复 ${r.fixed} 项,失败 ${r.failed} 项`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
165
190
|
return reportAndExit(issues, opts);
|
|
166
191
|
}
|
|
167
192
|
|
package/src/tts-deps.js
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* TTS provider 依赖检查(最硬核版)
|
|
5
|
+
*
|
|
6
|
+
* 检查每个 provider 需要的所有东西:
|
|
7
|
+
* xf: crypto-js, ws, log4node (Node 包) + appid/apiSecret/apiKey (凭证)
|
|
8
|
+
* minimax: token (凭证) - Node 内置 https,无外部依赖
|
|
9
|
+
* edge: python3, edge-tts (pip 包), ffmpeg (系统) - 完全免费但依赖多
|
|
10
|
+
*
|
|
11
|
+
* 用法:
|
|
12
|
+
* const issues = checkDeps(workdir);
|
|
13
|
+
* // issues: [{severity, msg, fixCmd, autoFixable}]
|
|
14
|
+
* if (opts.fix) fixDeps(workdir, issues);
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const { execSync, execFileSync } = require('child_process');
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
|
|
21
|
+
const PROVIDER_DEPENDENCIES = {
|
|
22
|
+
xf: {
|
|
23
|
+
nodeModules: ['crypto-js', 'ws', 'log4node'],
|
|
24
|
+
creds: [
|
|
25
|
+
{ key: 'TTS.xf_appid', name: 'appid' },
|
|
26
|
+
{ key: 'TTS.xf_apiSecret', name: 'apiSecret' },
|
|
27
|
+
{ key: 'TTS.xf_apiKey', name: 'apiKey' }
|
|
28
|
+
],
|
|
29
|
+
systemPkgs: [],
|
|
30
|
+
pipPkgs: [],
|
|
31
|
+
executables: []
|
|
32
|
+
},
|
|
33
|
+
minimax: {
|
|
34
|
+
nodeModules: [], // 用 Node 内置 https
|
|
35
|
+
creds: [{ key: 'TTS.minimax_token', name: 'token' }],
|
|
36
|
+
systemPkgs: [],
|
|
37
|
+
pipPkgs: [],
|
|
38
|
+
executables: []
|
|
39
|
+
},
|
|
40
|
+
edge: {
|
|
41
|
+
nodeModules: [],
|
|
42
|
+
creds: [],
|
|
43
|
+
systemPkgs: [
|
|
44
|
+
{ name: 'python3', cmd: 'python3', altCmd: 'python' },
|
|
45
|
+
{ name: 'ffmpeg', cmd: 'ffmpeg' }
|
|
46
|
+
],
|
|
47
|
+
pipPkgs: [{ pkg: 'edge-tts', import: 'edge_tts' }],
|
|
48
|
+
executables: [{ name: 'ffmpeg', cmd: 'ffmpeg' }]
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
function which(cmd) {
|
|
53
|
+
try {
|
|
54
|
+
execSync(`command -v ${cmd} 2>/dev/null || which ${cmd} 2>/dev/null`, { stdio: 'pipe' });
|
|
55
|
+
return true;
|
|
56
|
+
} catch (e) { return false; }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function findPythonCmd() {
|
|
60
|
+
for (const c of ['python3', 'python']) {
|
|
61
|
+
if (which(c)) {
|
|
62
|
+
try {
|
|
63
|
+
const v = execSync(`${c} --version`, { stdio: 'pipe', encoding: 'utf8' });
|
|
64
|
+
if (/Python 3\./.test(v)) return c;
|
|
65
|
+
} catch (e) {}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function pythonHasModule(pyCmd, modName) {
|
|
72
|
+
if (!pyCmd) return false;
|
|
73
|
+
try {
|
|
74
|
+
execFileSync(pyCmd, ['-c', `import ${modName}`], { stdio: 'pipe' });
|
|
75
|
+
return true;
|
|
76
|
+
} catch (e) { return false; }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function nodeModuleInstalled(workdir, modName) {
|
|
80
|
+
try {
|
|
81
|
+
require.resolve(modName, { paths: [workdir] });
|
|
82
|
+
return true;
|
|
83
|
+
} catch (e) { return false; }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function getCredValue(cfg, dottedKey) {
|
|
87
|
+
const parts = dottedKey.split('.');
|
|
88
|
+
let cur = cfg;
|
|
89
|
+
for (const p of parts) {
|
|
90
|
+
if (cur == null || typeof cur !== 'object') return undefined;
|
|
91
|
+
cur = cur[p];
|
|
92
|
+
}
|
|
93
|
+
return cur;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function detectAptLike() {
|
|
97
|
+
if (which('apt-get')) return 'apt-get';
|
|
98
|
+
if (which('apt')) return 'apt';
|
|
99
|
+
if (which('yum')) return 'yum';
|
|
100
|
+
if (which('dnf')) return 'dnf';
|
|
101
|
+
if (which('apk')) return 'apk';
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 检查指定 provider 的所有依赖
|
|
107
|
+
* @returns {issues: Array, provider: string}
|
|
108
|
+
*/
|
|
109
|
+
function checkDeps(workdir, providerName, cfg) {
|
|
110
|
+
const issues = [];
|
|
111
|
+
providerName = providerName || (cfg && cfg.TTS && cfg.TTS.provider) || 'xf';
|
|
112
|
+
const deps = PROVIDER_DEPENDENCIES[providerName];
|
|
113
|
+
if (!deps) {
|
|
114
|
+
return { issues: [{ severity: 'error', msg: `未知 provider: ${providerName}`, autoFixable: false }], provider: providerName };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 1. 凭证
|
|
118
|
+
for (const c of deps.creds) {
|
|
119
|
+
const val = getCredValue(cfg, c.key);
|
|
120
|
+
if (!val) {
|
|
121
|
+
issues.push({
|
|
122
|
+
severity: 'error',
|
|
123
|
+
category: 'cred',
|
|
124
|
+
msg: `${providerName} 缺凭证 ${c.name}(${c.key})`,
|
|
125
|
+
fixCmd: `adoremix config set ${c.key} <your_${c.name}>`,
|
|
126
|
+
autoFixable: false
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 2. Node 模块
|
|
132
|
+
for (const mod of deps.nodeModules) {
|
|
133
|
+
if (!nodeModuleInstalled(workdir, mod)) {
|
|
134
|
+
issues.push({
|
|
135
|
+
severity: 'error',
|
|
136
|
+
category: 'node_module',
|
|
137
|
+
msg: `Node 包未安装: ${mod}`,
|
|
138
|
+
fixCmd: `cd ${workdir} && npm install ${mod}`,
|
|
139
|
+
autoFixable: true,
|
|
140
|
+
fixType: 'npm',
|
|
141
|
+
fixArgs: { cwd: workdir, mod }
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 3. pip 包
|
|
147
|
+
const pyCmd = findPythonCmd();
|
|
148
|
+
for (const pip of deps.pipPkgs) {
|
|
149
|
+
if (!pythonHasModule(pyCmd, pip.import)) {
|
|
150
|
+
issues.push({
|
|
151
|
+
severity: 'error',
|
|
152
|
+
category: 'pip',
|
|
153
|
+
msg: `Python 包未安装: ${pip.pkg}`,
|
|
154
|
+
fixCmd: 'pip3 install ' + pip.pkg,
|
|
155
|
+
autoFixable: true,
|
|
156
|
+
fixType: 'pip',
|
|
157
|
+
fixArgs: { pkg: pip.pkg, pyCmd }
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 4. 系统可执行
|
|
163
|
+
for (const exe of deps.execubles || deps.systemPkgs) {
|
|
164
|
+
if (exe.cmd && !which(exe.cmd)) {
|
|
165
|
+
// 推断 apt 包名
|
|
166
|
+
let aptPkg = '';
|
|
167
|
+
if (exe.name === 'python3') aptPkg = 'python3 python3-pip';
|
|
168
|
+
else if (exe.name === 'ffmpeg') aptPkg = 'ffmpeg';
|
|
169
|
+
const pkgMgr = detectAptLike();
|
|
170
|
+
issues.push({
|
|
171
|
+
severity: 'error',
|
|
172
|
+
category: 'system',
|
|
173
|
+
msg: `系统命令未找到: ${exe.name}`,
|
|
174
|
+
fixCmd: aptPkg && pkgMgr ? `sudo ${pkgMgr} install -y ${aptPkg}` : '',
|
|
175
|
+
autoFixable: !!(aptPkg && pkgMgr),
|
|
176
|
+
fixType: 'system',
|
|
177
|
+
fixArgs: { pkgMgr, aptPkg }
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// python3 特殊处理(如果 edge 需要 python3 但只装了 python)
|
|
183
|
+
if (providerName === 'edge' && !pyCmd) {
|
|
184
|
+
const pkgMgr = detectAptLike();
|
|
185
|
+
issues.push({
|
|
186
|
+
severity: 'error',
|
|
187
|
+
category: 'system',
|
|
188
|
+
msg: '需要 python3(未找到 Python 3.x)',
|
|
189
|
+
fixCmd: pkgMgr ? `sudo ${pkgMgr} install -y python3 python3-pip` : '',
|
|
190
|
+
autoFixable: !!pkgMgr,
|
|
191
|
+
fixType: 'system',
|
|
192
|
+
fixArgs: { pkgMgr, aptPkg: 'python3 python3-pip' }
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return { issues, provider: providerName };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* 自动修复(仅 autoFixable=true 的)
|
|
201
|
+
*/
|
|
202
|
+
function fixDeps(workdir, issues, opts) {
|
|
203
|
+
opts = opts || {};
|
|
204
|
+
const sudo = opts.sudo ? 'sudo ' : '';
|
|
205
|
+
let fixed = 0;
|
|
206
|
+
let failed = 0;
|
|
207
|
+
for (const issue of issues) {
|
|
208
|
+
if (!issue.autoFixable) continue;
|
|
209
|
+
try {
|
|
210
|
+
if (issue.fixType === 'npm') {
|
|
211
|
+
execSync(`npm install ${issue.fixArgs.mod} --no-audit --no-fund`, {
|
|
212
|
+
cwd: issue.fixArgs.cwd,
|
|
213
|
+
stdio: 'inherit'
|
|
214
|
+
});
|
|
215
|
+
} else if (issue.fixType === 'pip') {
|
|
216
|
+
const py = issue.fixArgs.pyCmd || findPythonCmd() || 'python3';
|
|
217
|
+
execSync(`${sudo}${py} -m pip install ${issue.fixArgs.pkg}`, { stdio: 'inherit' });
|
|
218
|
+
} else if (issue.fixType === 'system') {
|
|
219
|
+
if (!issue.fixArgs.pkgMgr) throw new Error('包管理器未识别');
|
|
220
|
+
execSync(`${sudo}${issue.fixArgs.pkgMgr} install -y ${issue.fixArgs.aptPkg}`, { stdio: 'inherit' });
|
|
221
|
+
}
|
|
222
|
+
fixed++;
|
|
223
|
+
} catch (e) {
|
|
224
|
+
console.error(` ✗ 修复失败 [${issue.msg}]: ${e.message.split('\n')[0]}`);
|
|
225
|
+
failed++;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return { fixed, failed };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* 列出所有 provider 的依赖(用于 adoremix tts deps 不带 provider 时)
|
|
233
|
+
*/
|
|
234
|
+
function listAllProviders(workdir, cfg) {
|
|
235
|
+
const result = {};
|
|
236
|
+
for (const name of Object.keys(PROVIDER_DEPENDENCIES)) {
|
|
237
|
+
const r = checkDeps(workdir, name, cfg);
|
|
238
|
+
result[name] = {
|
|
239
|
+
issues: r.issues,
|
|
240
|
+
ok: r.issues.length === 0
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
return result;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
module.exports = {
|
|
247
|
+
PROVIDER_DEPENDENCIES,
|
|
248
|
+
checkDeps,
|
|
249
|
+
fixDeps,
|
|
250
|
+
listAllProviders,
|
|
251
|
+
which,
|
|
252
|
+
findPythonCmd
|
|
253
|
+
};
|
package/tts/tts.js
CHANGED
|
@@ -64,10 +64,12 @@ async function main() {
|
|
|
64
64
|
const outFile = path.join(ttyDir, mp3Name);
|
|
65
65
|
|
|
66
66
|
// 凭证:优先 config.ini,命令行参数兼容旧版 xf
|
|
67
|
+
// 兼容 Qt 已有字段:Settings.ttsxfAPPID / ttsxfAPISecret / ttsxfAPIKey
|
|
68
|
+
const settings = cfg.Settings || {};
|
|
67
69
|
const creds = {
|
|
68
|
-
xf_appid: ttsSection.xf_appid || appidArg || '',
|
|
69
|
-
xf_apiSecret: ttsSection.xf_apiSecret || apiSecretArg || '',
|
|
70
|
-
xf_apiKey: ttsSection.xf_apiKey || apiKeyArg || '',
|
|
70
|
+
xf_appid: ttsSection.xf_appid || settings.ttsxfAPPID || appidArg || '',
|
|
71
|
+
xf_apiSecret: ttsSection.xf_apiSecret || settings.ttsxfAPISecret || apiSecretArg || '',
|
|
72
|
+
xf_apiKey: ttsSection.xf_apiKey || settings.ttsxfAPIKey || apiKeyArg || '',
|
|
71
73
|
xf_voice_override: ttsSection.xf_voice_override || '',
|
|
72
74
|
minimax_token: ttsSection.minimax_token || '',
|
|
73
75
|
edge_voice_override: ttsSection.edge_voice_override || ''
|