@oxiaom/adoremix 1.0.42 → 1.0.44

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxiaom/adoremix",
3
- "version": "1.0.42",
3
+ "version": "1.0.44",
4
4
  "description": "AdoreMix broadcast server - cross-platform installer, runner and service manager",
5
5
  "bin": {
6
6
  "adoremix": "./bin/adoremix.js"
package/src/cli.js CHANGED
@@ -471,11 +471,8 @@ function buildProgram() {
471
471
 
472
472
  audio8
473
473
  .command('install')
474
- .description('通过 SSH 远程安装 Audio8 TTS Linux 主机(默认 192.168.1.114:8024),自动启动 systemd 服务')
475
- .option('--host <ip>', '远程主机 IP', '192.168.1.114')
476
- .option('--user <name>', 'SSH 用户', 'oxiaom')
477
- .option('--pass <pwd>', 'SSH 密码')
478
- .option('--workdir <path>', '本地工作目录(写入 config.ini 用)')
474
+ .description('在本机(Linux)自动安装 Audio8 TTS + systemd 服务(端口 8024),改本地 config.ini;Windows 不支持')
475
+ .option('--workdir <path>', 'AdoreMix 工作目录(写入 config.ini 用)')
479
476
  .action(async (opts) => {
480
477
  const mod = require('../tts/providers/audio8');
481
478
  try {
@@ -488,10 +485,7 @@ function buildProgram() {
488
485
 
489
486
  audio8
490
487
  .command('uninstall')
491
- .description('卸载远程 Linux 上的 Audio8 TTS 服务')
492
- .option('--host <ip>', '远程主机 IP', '192.168.1.114')
493
- .option('--user <name>', 'SSH 用户', 'oxiaom')
494
- .option('--pass <pwd>', 'SSH 密码')
488
+ .description('卸载本机的 Audio8 TTS 服务')
495
489
  .action(async (opts) => {
496
490
  const mod = require('../tts/providers/audio8');
497
491
  try {
@@ -504,9 +498,8 @@ function buildProgram() {
504
498
 
505
499
  audio8
506
500
  .command('status')
507
- .description('检查远程 Audio8 TTS 服务健康')
508
- .option('--host <ip>', '远程主机 IP', '192.168.1.114')
509
- .option('--url <url>', '自定义 base_url')
501
+ .description('检查本机 Audio8 TTS 服务健康(GET /v1/models)')
502
+ .option('--url <url>', '自定义 base_url,默认 http://127.0.0.1:8024')
510
503
  .action(async (opts) => {
511
504
  const mod = require('../tts/providers/audio8');
512
505
  try {
@@ -19,13 +19,14 @@
19
19
  * 声音克隆:3~10 秒参考音频,但需在 Audio8 服务端预设(请求时用 voice 参数指定)
20
20
  */
21
21
 
22
- const { execFileSync } = require('child_process');
22
+ const { execFileSync, execSync } = require('child_process');
23
23
  const fs = require('fs');
24
24
  const path = require('path');
25
25
  const os = require('os');
26
26
  const https = require('https');
27
27
  const http = require('http');
28
28
  const { URL } = require('url');
29
+ const logger = require('../../src/logger');
29
30
 
30
31
  // 中文常用 voice(Audio8 的 zh-CN voices,按性别分组)
31
32
  const VOICES = {
@@ -158,8 +159,9 @@ function synthesize({ text, voice, volume, speed, outFile }, creds) {
158
159
  }
159
160
 
160
161
  // ===== audio8 install / uninstall / status(类似 nginx install 的可选组件)=====
161
- // 通过 SSH 远程部署到 Linux 主机,Windows 本机调用只需装 plink。
162
- // 默认目标:192.168.1.114:22 / 用户 oxiaom。改 SSH_TARGET/SSH_USER 环境变量或 --host/--user 参数即可换机器。
162
+ // **本机部署**:在当前 adoremix 进程所在机器上直接装(不是 SSH)。
163
+ // 各机器跑 `adoremix audio8 install` 就在自己身上起 Audio8 TTS 服务。
164
+ // 仅 Linux 可装(依赖 Python3 + venv + systemd),Windows/macOS 直接提示不支持。
163
165
  const AUDIO8_DEFAULT_PORT = 8024;
164
166
  const AUDIO8_REPO = 'https://github.com/Audio8-AI/Audio8_TTS.git';
165
167
  const AUDIO8_DEPLOY_DIR = '~/audio8';
@@ -169,25 +171,40 @@ const AUDIO8_SYSTEMD_NAME = 'audio8-tts';
169
171
  const AUDIO8_SYSTEMD_PATH = '/etc/systemd/system/audio8-tts.service';
170
172
  const AUDIO8_HEALTH_URL = `http://127.0.0.1:${AUDIO8_DEFAULT_PORT}/v1/models`;
171
173
 
172
- function plink() {
173
- for (const cmd of ['plink', '"C:\\Program Files\\PuTTY\\plink.exe"']) {
174
- try { return execSync(cmd.replace(/"/g, '').split(' ')[0], { stdio: 'pipe' }); } catch (e) {}
175
- }
176
- try { return require('child_process').execSync('where plink', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim().split('\n')[0]; } catch (e) {}
174
+ // 包管理器嗅探(apt/dnf/yum/apk/pacman)
175
+ function detectPkgMgr() {
176
+ try {
177
+ if (execFileSync('which apt-get', { stdio: 'pipe' }) && true) return 'apt';
178
+ } catch (e) {}
179
+ try {
180
+ if (execFileSync('which dnf', { stdio: 'pipe' }) && true) return 'dnf';
181
+ } catch (e) {}
182
+ try {
183
+ if (execFileSync('which yum', { stdio: 'pipe' }) && true) return 'yum';
184
+ } catch (e) {}
185
+ try {
186
+ if (execFileSync('which apk', { stdio: 'pipe' }) && true) return 'apk';
187
+ } catch (e) {}
188
+ try {
189
+ if (execFileSync('which pacman', { stdio: 'pipe' }) && true) return 'pacman';
190
+ } catch (e) {}
177
191
  return null;
178
192
  }
179
193
 
180
- function sshExec(cmd, opts) {
181
- opts = opts || {};
182
- const host = opts.host || process.env.ADOREMIX_AUDIO8_SSH_HOST || '192.168.1.114';
183
- const user = opts.user || process.env.ADOREMIX_AUDIO8_SSH_USER || 'oxiaom';
184
- const pass = opts.pass || process.env.ADOREMIX_AUDIO8_SSH_PASS || '123123';
185
- const pl = plink();
186
- if (!pl) throw new Error('未找到 plink(PuTTY),请安装 PuTTY 或将其加入 PATH');
187
- return execSync(`"${pl}" -ssh -batch -pw "${pass}" ${user}@${host} '${cmd.replace(/'/g, "'\\''")}'`, { stdio: 'pipe', encoding: 'utf8' });
194
+ function systemInstallCmd(pkgMgr, pkgs) {
195
+ // pkgs: { name: 'pkg-name' } 数组
196
+ const list = pkgs.map(p => p.name).join(' ');
197
+ switch (pkgMgr) {
198
+ case 'apt': return `apt-get update && apt-get install -y ${list}`;
199
+ case 'dnf':
200
+ case 'yum': return `${pkgMgr} install -y ${list}`;
201
+ case 'apk': return `apk add ${list}`;
202
+ case 'pacman': return `pacman -Sy --noconfirm ${list}`;
203
+ default: return null;
204
+ }
188
205
  }
189
206
 
190
- function buildSystemdUnit(pyCmd) {
207
+ function buildSystemdUnit() {
191
208
  return `[Unit]
192
209
  Description=Audio8 TTS (AdoreMix)
193
210
  After=network.target
@@ -208,94 +225,169 @@ WantedBy=multi-user.target
208
225
 
209
226
  function install(opts) {
210
227
  opts = opts || {};
211
- logger.ok('开始远程安装 Audio8 TTS(目标: ' + (opts.host || '192.168.1.114') + ')');
212
- const cmds = [
213
- 'echo "=== 系统检测 ==="',
214
- 'python3 --version',
215
- 'pip3 --version',
216
- 'git --version',
217
- 'echo "=== 安装系统依赖 ==="',
218
- 'apt-get update && apt-get install -y git python3-pip python3-venv ffmpeg',
219
- 'echo "=== clone 仓库(如果不存在)==="',
220
- `if [ ! -d ${AUDIO8_DEPLOY_DIR} ]; then git clone ${AUDIO8_REPO} ${AUDIO8_DEPLOY_DIR}; else echo "已存在,跳过"; fi`,
221
- 'echo "=== 创建 venv 并装依赖 ==="',
222
- `cd ${AUDIO8_DEPLOY_DIR}/onnx_runtime && python3 -m venv .venv`,
223
- `${AUDIO8_VENV_DIR}/bin/pip install -U pip`,
224
- `${AUDIO8_VENV_DIR}/bin/pip install -r ${AUDIO8_REQUIREMENTS}`,
225
- 'echo "=== 写 systemd 单元 ==="',
226
- // systemd 单元写入使用 heredoc 转义(这里通过 echo + 重定向)
227
- `bash -c 'cat > ${AUDIO8_SYSTEMD_PATH} <<\"EOF\"\n${buildSystemdUnit()}\nEOF'`,
228
- 'systemctl daemon-reload',
229
- `systemctl enable ${AUDIO8_SYSTEMD_NAME}`,
230
- `systemctl restart ${AUDIO8_SYSTEMD_NAME}`,
231
- 'echo "=== 等待服务健康(首次启动要下载 ~572MB 模型,最多 60s)==="',
232
- `for i in $(seq 1 60); do sleep 2; if curl -sf ${AUDIO8_HEALTH_URL} >/dev/null 2>&1; then echo "OK after ${i} attempts"; break; fi; done`,
233
- `curl -sf ${AUDIO8_HEALTH_URL} >/dev/null && echo "✓ Audio8 服务健康: http://127.0.0.1:${AUDIO8_DEFAULT_PORT}" || echo "⚠ 60s 内未就绪,可手动 systemctl status ${AUDIO8_SYSTEMD_NAME}"`
228
+ // 平台检查
229
+ if (process.platform !== 'linux') {
230
+ logger.error(`Audio8 TTS 仅支持 Linux(当前 ${process.platform},Windows/macOS 上 ffmpeg/venv/systemd 不可用)`);
231
+ logger.log(`如需在 Windows 本机用 Audio8,请用 WSL2 或部署到 Linux 主机后通过 config.ini audio8_base_url 远程调用。`);
232
+ return 1;
233
+ }
234
+
235
+ // 探测包管理器
236
+ const pkgMgr = detectPkgMgr();
237
+ if (!pkgMgr) {
238
+ logger.error('未识别到包管理器(apt/dnf/yum/apk/pacman 均未找到)。Audio8 install 仅支持这些主流 Linux 发行版。');
239
+ return 1;
240
+ }
241
+ logger.ok(`检测到包管理器: ${pkgMgr}`);
242
+
243
+ // 包映射(按发行版提供 ffmpeg/git/python3)
244
+ // ffmpeg RHEL 系官方源没有,要先装 epel-release
245
+ let sysPkgs = [
246
+ { name: 'git' },
247
+ { name: 'python3' },
248
+ { name: 'python3-pip' }, // Debian/Ubuntu
249
+ { name: 'python3-venv' },
250
+ { name: 'ffmpeg' }
234
251
  ];
235
- const fullCmd = cmds.join(' && ');
252
+ if (pkgMgr === 'dnf' || pkgMgr === 'yum') {
253
+ sysPkgs = [
254
+ { name: 'git' },
255
+ { name: 'python3' },
256
+ { name: 'python3-pip' },
257
+ { name: 'python3-virtualenv' }, // RHEL 系包名不同
258
+ { name: 'epel-release' }, // ffmpeg 在 EPEL
259
+ { name: 'ffmpeg' }
260
+ ];
261
+ }
262
+ if (pkgMgr === 'apk') {
263
+ sysPkgs = [
264
+ { name: 'git' },
265
+ { name: 'python3' },
266
+ { name: 'py3-pip' },
267
+ { name: 'ffmpeg' }
268
+ ];
269
+ }
270
+
271
+ try {
272
+ logger.log('=== 安装系统依赖 ===');
273
+ execSync(systemInstallCmd(pkgMgr, sysPkgs), { stdio: 'inherit' });
274
+ if (pkgMgr === 'dnf' || pkgMgr === 'yum') {
275
+ // EPEL 装好后再装 ffmpeg
276
+ try { execSync(`${pkgMgr} install -y ffmpeg --enablerepo=epel`, { stdio: 'inherit' }); } catch (e) { logger.warn('ffmpeg 安装失败(EPEL 装好后重试): ' + e.message); }
277
+ }
278
+ } catch (e) {
279
+ logger.error('系统依赖安装失败: ' + e.message.slice(0, 200));
280
+ return 1;
281
+ }
282
+
283
+ // git clone
236
284
  try {
237
- const out = sshExec(fullCmd, opts);
238
- process.stdout.write(out);
285
+ logger.log('=== 克隆 Audio8_TTS 仓库 ===');
286
+ execSync(`mkdir -p ~/audio8 && if [ ! -d ~/audio8/.git ]; then git clone ${AUDIO8_REPO} ~/audio8_temp && mv ~/audio8_temp/* ~/audio8_temp/.[!.]* ~/audio8/ 2>/dev/null; rm -rf ~/audio8_temp; else cd ~/audio8 && git pull; fi`, { stdio: 'inherit', shell: '/bin/bash' });
239
287
  } catch (e) {
240
- logger.error('SSH 执行失败:' + e.message.slice(0, 200));
288
+ logger.error('克隆失败: ' + e.message.slice(0, 200));
241
289
  return 1;
242
290
  }
243
- // 修改本地 config.ini 的 [TTS] provider/base_url
291
+
292
+ // venv + pip install
293
+ try {
294
+ logger.log('=== 创建 venv 并安装 Python 依赖(首次需 5-10 分钟下载 torch 等)===');
295
+ execSync('python3 -m venv ~/.audio8_venv', { stdio: 'inherit', shell: '/bin/bash' });
296
+ execSync('source ~/.audio8_venv/bin/activate && pip install -U pip --quiet && pip install -r ~/audio8/onnx_runtime/requirements.txt --quiet', { stdio: 'inherit', shell: '/bin/bash' });
297
+ } catch (e) {
298
+ logger.error('Python 依赖安装失败: ' + e.message.slice(0, 200));
299
+ return 1;
300
+ }
301
+
302
+ // 写 systemd 单元
303
+ try {
304
+ fs.writeFileSync(AUDIO8_SYSTEMD_PATH, buildSystemdUnit(), 'utf8');
305
+ execSync('systemctl daemon-reload', { stdio: 'inherit', shell: '/bin/bash' });
306
+ execSync(`systemctl enable ${AUDIO8_SYSTEMD_NAME}`, { stdio: 'inherit', shell: '/bin/bash' });
307
+ execSync(`systemctl restart ${AUDIO8_SYSTEMD_NAME}`, { stdio: 'inherit', shell: '/bin/bash' });
308
+ } catch (e) {
309
+ logger.error('systemd 配置失败(无 systemd 容器?手动启动: ~/audio8_venv/bin/python -m arktts_runtime.service): ' + e.message.slice(0, 200));
310
+ return 1;
311
+ }
312
+
313
+ // 等服务健康(含下载 572MB 模型,冷启动最多 60s)
314
+ logger.log('=== 等待服务健康(首次启动要下载 ~572MB 模型,最多 60 秒)===');
315
+ let ok = false;
316
+ for (let i = 1; i <= 30; i++) {
317
+ try {
318
+ execSync(`curl -sf ${AUDIO8_HEALTH_URL} >/dev/null 2>&1`);
319
+ logger.ok(`Audio8 服务在 ${i*2} 秒后就绪: ${AUDIO8_HEALTH_URL}`);
320
+ ok = true;
321
+ break;
322
+ } catch (e) {}
323
+ execSync('sleep 2', { stdio: 'pipe', shell: '/bin/bash' });
324
+ }
325
+ if (!ok) {
326
+ logger.warn('60 秒内未就绪,请手动 systemctl status ' + AUDIO8_SYSTEMD_NAME);
327
+ logger.log('也可直接前台启动测试: source ~/.audio8_venv/bin/activate && python -m arktts_runtime.service --model-dir ~/audio8/model --voices-dir ~/audio8/model/voices --port ' + AUDIO8_DEFAULT_PORT);
328
+ }
329
+
330
+ // 改本地 config.ini
244
331
  try {
245
332
  const cfg = require(path.join(__dirname, '..', 'src', 'config'));
246
- const localHost = opts.host || '192.168.1.114';
247
- cfg.setConfigValue(opts.workdir || path.join(os.homedir(), '.local', 'share', 'adoremix'), 'TTS.provider', 'audio8');
248
- cfg.setConfigValue(opts.workdir || path.join(os.homedir(), '.local', 'share', 'adoremix'), 'TTS.audio8_base_url', `http://${localHost}:${AUDIO8_DEFAULT_PORT}`);
249
- logger.ok(`本地 config.ini 已更新: provider=audio8, audio8_base_url=http://${localHost}:${AUDIO8_DEFAULT_PORT}`);
333
+ const wd = opts.workdir || process.env.ADOREMIX_WORKDIR || path.join(os.homedir(), '.local', 'share', 'adoremix');
334
+ cfg.setConfigValue(wd, 'TTS.provider', 'audio8');
335
+ cfg.setConfigValue(wd, 'TTS.audio8_base_url', `http://127.0.0.1:${AUDIO8_DEFAULT_PORT}`);
336
+ logger.ok(`config.ini 已更新: provider=audio8, audio8_base_url=http://127.0.0.1:${AUDIO8_DEFAULT_PORT}`);
250
337
  } catch (e) {
251
- logger.warn('本地 config.ini 更新失败(可手动设置):' + e.message.slice(0, 100));
338
+ logger.warn('config.ini 更新失败: ' + e.message);
252
339
  }
253
- logger.ok('Audio8 TTS 远程安装完成');
254
- logger.log(`访问: http://${opts.host || '192.168.1.114'}:${AUDIO8_DEFAULT_PORT}/docs`);
340
+
341
+ logger.ok(`Audio8 TTS 本机安装完成 → http://127.0.0.1:${AUDIO8_DEFAULT_PORT}`);
255
342
  return 0;
256
343
  }
257
344
 
258
345
  function uninstall(opts) {
259
- opts = opts || {};
260
- logger.ok('开始远程卸载 Audio8 TTS');
261
- const cmds = [
262
- `systemctl --now disable ${AUDIO8_SYSTEMD_NAME} 2>/dev/null; true`,
263
- `systemctl stop ${AUDIO8_SYSTEMD_NAME} 2>/dev/null; true`,
264
- `rm -f ${AUDIO8_SYSTEMD_PATH}`,
265
- 'systemctl daemon-reload',
266
- `rm -rf ${AUDIO8_DEPLOY_DIR}`,
267
- `rm -rf ~/.cache/huggingface/hub/models--Audio8*`
268
- ];
346
+ if (process.platform !== 'linux') {
347
+ logger.error('audio8 uninstall 仅 Linux 支持');
348
+ return 1;
349
+ }
269
350
  try {
270
- const out = sshExec(cmds.join(' && '), opts);
271
- process.stdout.write(out);
351
+ execSync(`systemctl --now disable ${AUDIO8_SYSTEMD_NAME} 2>/dev/null; true`, { stdio: 'pipe', shell: '/bin/bash' });
352
+ execSync(`systemctl stop ${AUDIO8_SYSTEMD_NAME} 2>/dev/null; true`, { stdio: 'pipe', shell: '/bin/bash' });
353
+ execSync(`rm -f ${AUDIO8_SYSTEMD_PATH} && systemctl daemon-reload`, { stdio: 'pipe', shell: '/bin/bash' });
354
+ execSync(`rm -rf ~/audio8 ~/audio8_venv`, { stdio: 'pipe', shell: '/bin/bash' });
355
+ execSync(`rm -rf ~/.cache/huggingface/hub/models--Audio8*`, { stdio: 'pipe', shell: '/bin/bash' });
272
356
  } catch (e) {
273
- logger.error('SSH 执行失败:' + e.message.slice(0, 200));
357
+ logger.warn('uninstall 清理部分出错: ' + e.message);
274
358
  return 1;
275
359
  }
276
- logger.ok('Audio8 TTS 远程卸载完成');
360
+ logger.ok('Audio8 TTS 已卸载');
277
361
  return 0;
278
362
  }
279
363
 
280
364
  async function status(opts) {
281
365
  opts = opts || {};
282
- const baseUrl = (opts.url || process.env.ADOREMIX_AUDIO8_BASE_URL || `http://${opts.host || '192.168.1.114'}:${AUDIO8_DEFAULT_PORT}`);
283
- const urlObj = new URL(`${baseUrl}/v1/models`);
284
- const lib = urlObj.protocol === 'https:' ? require('https') : http;
285
- return new Promise(resolve => {
286
- const req = lib.request({ hostname: urlObj.hostname, port: urlObj.port, path: urlObj.pathname, method: 'GET', timeout: 5000 }, res => {
287
- if (res.statusCode === 200) {
288
- logger.ok(`Audio8 服务健康: ${baseUrl}/v1/models 返回 ${res.statusCode}`);
289
- resolve(0);
290
- } else {
291
- logger.warn(`Audio8 服务异常: ${baseUrl} 返回 ${res.statusCode}`);
292
- resolve(1);
293
- }
366
+ const baseUrl = opts.url || process.env.ADOREMIX_AUDIO8_BASE_URL || `http://127.0.0.1:${AUDIO8_DEFAULT_PORT}`;
367
+ try {
368
+ const urlObj = new URL(`${baseUrl}/v1/models`);
369
+ const lib = urlObj.protocol === 'https:' ? require('https') : require('http');
370
+ await new Promise(resolve => {
371
+ const req = lib.request({ hostname: urlObj.hostname, port: urlObj.port, path: urlObj.pathname, method: 'GET', timeout: 5000 }, res => {
372
+ if (res.statusCode === 200) {
373
+ logger.ok(`Audio8 服务健康: ${baseUrl}/v1/models 返回 ${res.statusCode}`);
374
+ res.resume();
375
+ resolve(0);
376
+ } else {
377
+ logger.warn(`Audio8 服务异常: ${baseUrl} 返回 ${res.statusCode}`);
378
+ res.resume();
379
+ resolve(1);
380
+ }
381
+ });
382
+ req.on('error', err => { logger.warn(`Audio8 服务不可达: ${err.message}`); resolve(1); });
383
+ req.on('timeout', () => { req.destroy(); logger.warn(`Audio8 服务超时: ${baseUrl}`); resolve(1); });
384
+ req.end();
294
385
  });
295
- req.on('error', err => { logger.warn(`Audio8 服务不可达: ${err.message}`); resolve(1); });
296
- req.on('timeout', () => { req.destroy(); logger.warn(`Audio8 服务超时: ${baseUrl}`); resolve(1); });
297
- req.end();
298
- });
386
+ return 0;
387
+ } catch (e) {
388
+ logger.warn(`Audio8 status 检查失败: ${e.message}`);
389
+ return 1;
390
+ }
299
391
  }
300
392
 
301
393
  module.exports = { synthesize, checkDeps, VOICES, DEFAULT_BASE_URL, install, uninstall, status };