Skill: zhangalexhy/xiaomi-tts (version 1.0.0)

--- name: xiaomi-tts description: 把文字转成语音。可以发语音、念给我听、唱歌、用方言或夹子音说话，支持各种情绪和风格。 --- # MiMo TTS 语音合成 ## 快速使用 ```bash node scripts/xiaomi-tts.js "你好世界" --voice default_zh --style "夹子音" --output output.wav ``` 需要设置 `MIMO_API_KEY` 环境变量，或通过 `--api-key` 参数传入。 ## 可用音色 | 音色 | voice 参数 | |------|-----------| | MiMo 默认 | `mimo_default` | | 中文女声（默认） | `default_zh` | | 英文女声 | `default_eh` | ## 风格控制 在文本开头用 `<style>` 标签指定风格，支持叠加： - 语速：变快 / 变慢 - 情绪：开心 / 悲伤 / 生气 - 角色：孙悟空 / 林黛玉 - 风格：悄悄话 / 夹子音 / 台湾腔 - 方言：东北话 / 四川话 / 河南话 / 粤语 - 唱歌：`<style>唱歌</style>歌词内容` ## 音频标签（细粒度控制） 在文本中用中文括号标注情绪、动作等： ``` （紧张，深呼吸）呼……冷静，冷静 （语速加快，碎碎念）怎么办怎么办 （小声）哎呀，领带歪没歪？ （提高音量喊话）大姐！这鱼新鲜着呢！ ``` ## 注意事项 - 合成文本放在 `assistant` 角色消息中 - `<style>` 标签必须在文本最前面 - 返回 base64 编码的 WAV 音频

path: scripts/xiaomi-tts.js
content: #!/usr/bin/env node
/**
 * MiMo TTS 语音合成脚本
 * 用法: node xiaomi-tts.js "要合成的文本" [--voice default_zh] [--style 夹子音] [--output out.wav]
 */

import { writeFileSync, readFileSync, existsSync } from "node:fs";
import { join } from "node:path";
import { parseArgs } from "node:util";
import { homedir } from "node:os";

const API_URL = "https://api.xiaomimimo.com/v1/chat/completions";
const MODEL = "mimo-v2-tts";
const DEFAULT_VOICE = "default_zh";

function resolveApiKey(override) {
  if (override) return override;
  if (process.env.MIMO_API_KEY) return process.env.MIMO_API_KEY;

  const configPath = join(homedir(), ".openclaw", "config.json");
  if (existsSync(configPath)) {
    try {
      const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
      if (cfg.mimo_api_key || cfg.MIMO_API_KEY) return cfg.mimo_api_key || cfg.MIMO_API_KEY;
    } catch { /* ignore */ }
  }

  console.error("错误: 未找到 MIMO_API_KEY，请通过环境变量或 --api-key 参数提供");
  process.exit(1);
}

function buildContent(text, style) {
  return style ? `<style>${style}</style>${text}` : text;
}

async function tts(text, { voice = DEFAULT_VOICE, style = "", apiKey }) {
  const res = await fetch(API_URL, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${apiKey}`,
    },
    body: JSON.stringify({
      model: MODEL,
      messages: [
        { role: "user", content: "你好" },
        { role: "assistant", content: buildContent(text, style) },
      ],
      audio: { format: "wav", voice },
    }),
  });

  if (!res.ok) {
    const body = await res.text().catch(() => "");
    throw new Error(`API 错误 ${res.status}: ${body || res.statusText}`);
  }

  const data = await res.json();
  const audioData = data?.choices?.[0]?.message?.audio?.data;
  if (!audioData) {
    throw new Error("API 返回数据中未包含音频内容");
  }

  return Buffer.from(audioData, "base64");
}

async function main() {
  const { values, positionals } = parseArgs({
    allowPositionals: true,
    options: {
      voice:     { type: "string", short: "v", default: DEFAULT_VOICE },
      style:     { type: "string", short: "s", default: "" },
      output:    { type: "string", short: "o" },
      "api-key": { type: "string", short: "k" },
    },
  });

  const text = positionals[0];
  if (!text) {
    console.error("用法: node xiaomi-tts.js \"要合成的文本\" [--voice default_zh] [--style 夹子音] [--output out.wav]");
    process.exit(1);
  }

  const [REDACTED_SECRET]"api-key"]);

  try {
    const audio = await tts(text, { voice: values.voice, style: values.style, apiKey });

    if (values.output) {
      writeFileSync(values.output, audio);
      console.log(`音频已保存: ${values.output}`);
    } else {
      process.stdout.write(audio);
    }
  } catch (e) {
    console.error(`合成失败: ${e.message}`);
    process.exit(1);
  }
}

main();


Security scan verdicts — ClawScan: clean | static analysis: suspicious | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)