mindcraft/src/agent/speak.js

88 lines
2.6 KiB
JavaScript
Raw Normal View History

2025-04-20 18:20:44 +01:00
import { exec, spawn } from 'child_process';
2025-04-21 08:06:19 +01:00
import { sendAudioRequest } from '../models/pollinations.js';
2025-02-12 16:26:48 +00:00
let speakingQueue = [];
let isSpeaking = false;
export function say(text, speak_model) {
speakingQueue.push([text, speak_model]);
2025-04-20 18:20:44 +01:00
if (!isSpeaking) processQueue();
}
2025-04-20 18:20:44 +01:00
async function processQueue() {
if (speakingQueue.length === 0) {
isSpeaking = false;
return;
}
isSpeaking = true;
const [txt, speak_model] = speakingQueue.shift();
2025-04-20 18:20:44 +01:00
const isWin = process.platform === 'win32';
const isMac = process.platform === 'darwin';
const model = speak_model || 'pollinations/openai-audio/echo';
2025-02-12 16:26:48 +00:00
2025-04-20 18:20:44 +01:00
if (model === 'system') {
// system TTS
const cmd = isWin
? `powershell -NoProfile -Command "Add-Type -AssemblyName System.Speech; \
$s=New-Object System.Speech.Synthesis.SpeechSynthesizer; $s.Rate=2; \
$s.Speak('${txt.replace(/'/g,"''")}'); $s.Dispose()"`
: isMac
? `say "${txt.replace(/"/g,'\\"')}"`
: `espeak "${txt.replace(/"/g,'\\"')}"`;
exec(cmd, err => {
if (err) console.error('TTS error', err);
processQueue();
});
2025-02-12 16:26:48 +00:00
} else {
2025-04-20 18:20:44 +01:00
// remote audio provider
let prov, mdl, voice, url;
if (typeof model === "string") {
[prov, mdl, voice] = model.split('/');
url = "https://text.pollinations.ai/openai";
} else {
2025-04-21 09:18:38 +01:00
prov = model.api;
mdl = model.model;
voice = model.voice;
url = model.url || "https://text.pollinations.ai/openai";
}
2025-04-20 18:20:44 +01:00
if (prov !== 'pollinations') throw new Error(`Unknown provider: ${prov}`);
2025-02-12 16:26:48 +00:00
2025-04-20 18:20:44 +01:00
try {
let audioData = await sendAudioRequest(txt, mdl, voice, url);
2025-04-21 08:06:19 +01:00
if (!audioData) {
2025-08-22 20:50:16 +01:00
throw new Error("TTS model did not return audio data");
// will be handled below
2025-04-21 08:06:19 +01:00
}
2025-04-20 18:20:44 +01:00
if (isWin) {
const ps = `
Add-Type -AssemblyName presentationCore;
$p=New-Object System.Windows.Media.MediaPlayer;
$p.Open([Uri]::new("data:audio/mp3;base64,${audioData}"));
$p.Play();
Start-Sleep -Seconds [math]::Ceiling($p.NaturalDuration.TimeSpan.TotalSeconds);
`;
spawn('powershell', ['-NoProfile','-Command', ps], {
stdio: 'ignore', detached: true
}).unref();
processQueue();
} else {
const player = spawn('ffplay', ['-nodisp','-autoexit','pipe:0'], {
stdio: ['pipe','ignore','ignore']
});
player.stdin.write(Buffer.from(audioData, 'base64'));
player.stdin.end();
player.on('exit', processQueue);
}
} catch (e) {
2025-08-22 20:50:16 +01:00
console.error('[TTS] Audio error', e);
2025-04-20 18:20:44 +01:00
processQueue();
2025-03-13 14:40:18 -05:00
}
2025-04-20 18:20:44 +01:00
}
2025-02-12 16:26:48 +00:00
}