100 lines
3.1 KiB
JavaScript
100 lines
3.1 KiB
JavaScript
|
|
const edgeTTS = require('edge-tts');
|
||
|
|
const path = require('path');
|
||
|
|
const fs = require('fs');
|
||
|
|
|
||
|
|
// 默认配置
|
||
|
|
const VOICE = "zh-CN-XiaoxiaoNeural";
|
||
|
|
const OUTPUT_DIR = "/root/.openclaw/media/tts";
|
||
|
|
|
||
|
|
async function textToSpeech(text, options = {}) {
|
||
|
|
const voice = options.voice || VOICE;
|
||
|
|
const rate = options.rate || "+0%";
|
||
|
|
const pitch = options.pitch || "+0Hz";
|
||
|
|
const outputFile = options.output || path.join(OUTPUT_DIR, `tts_${Date.now()}.mp3`);
|
||
|
|
|
||
|
|
// 确保输出目录存在
|
||
|
|
fs.mkdirSync(path.dirname(outputFile), { recursive: true });
|
||
|
|
|
||
|
|
try {
|
||
|
|
const communicate = edgeTTS.Communicate(text, voice, {
|
||
|
|
rate: rate,
|
||
|
|
pitch: pitch
|
||
|
|
});
|
||
|
|
|
||
|
|
await communicate.save(outputFile);
|
||
|
|
console.log(`✅ TTS 生成成功: ${outputFile}`);
|
||
|
|
return outputFile;
|
||
|
|
} catch (error) {
|
||
|
|
console.error(`❌ TTS 生成失败: ${error.message}`);
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async function listVoices() {
|
||
|
|
try {
|
||
|
|
const voices = await edgeTTS.getVoices();
|
||
|
|
console.log(`\n可用语音 (${voices.length} 个):\n`);
|
||
|
|
|
||
|
|
// 中文语音
|
||
|
|
const zhVoices = voices.filter(v => v.Lang.startsWith('zh'));
|
||
|
|
console.log("中文语音:");
|
||
|
|
zhVoices.forEach(v => console.log(` - ${v.Name} (${v.Lang})`));
|
||
|
|
|
||
|
|
// 英文语音
|
||
|
|
const enVoices = voices.filter(v => v.Lang.startsWith('en'));
|
||
|
|
console.log("\n英文语音:");
|
||
|
|
enVoices.forEach(v => console.log(` - ${v.Name} (${v.Lang})`));
|
||
|
|
|
||
|
|
return voices;
|
||
|
|
} catch (error) {
|
||
|
|
console.error(`获取语音列表失败: ${error.message}`);
|
||
|
|
return [];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 命令行参数处理
|
||
|
|
async function main() {
|
||
|
|
const args = process.argv.slice(2);
|
||
|
|
|
||
|
|
if (args.includes('--list-voices') || args.includes('-L')) {
|
||
|
|
await listVoices();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (args.length === 0) {
|
||
|
|
console.log("用法:");
|
||
|
|
console.log(" node tts-converter.js \"文本内容\"");
|
||
|
|
console.log(" node tts-converter.js \"文本\" --voice zh-CN-XiaoxiaoNeural --rate +10%");
|
||
|
|
console.log(" node tts-converter.js --list-voices");
|
||
|
|
console.log("\n选项:");
|
||
|
|
console.log(" --voice, -v 语音名称");
|
||
|
|
console.log(" --rate, -r 语速 (+/-百分比)");
|
||
|
|
console.log(" --pitch, -p 音调 (+/-Hz)");
|
||
|
|
console.log(" --output, -o 输出文件路径");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 解析参数
|
||
|
|
let text = args[0];
|
||
|
|
let options = {
|
||
|
|
voice: VOICE,
|
||
|
|
rate: "+0%",
|
||
|
|
pitch: "+0Hz",
|
||
|
|
output: path.join(OUTPUT_DIR, `tts_${Date.now()}.mp3`)
|
||
|
|
};
|
||
|
|
|
||
|
|
for (let i = 1; i < args.length; i += 2) {
|
||
|
|
const key = args[i].replace(/^--/, '');
|
||
|
|
const value = args[i + 1];
|
||
|
|
|
||
|
|
if (key === 'voice' || key === 'v') options.voice = value;
|
||
|
|
if (key === 'rate' || key === 'r') options.rate = value;
|
||
|
|
if (key === 'pitch' || key === 'p') options.pitch = value;
|
||
|
|
if (key === 'output' || key === 'o') options.output = value;
|
||
|
|
}
|
||
|
|
|
||
|
|
await textToSpeech(text, options);
|
||
|
|
}
|
||
|
|
|
||
|
|
main();
|