144 lines
4.7 KiB
JavaScript
144 lines
4.7 KiB
JavaScript
const express = require('express');
|
|
const WebSocket = require('ws');
|
|
const { spawn } = require('child_process');
|
|
const fs = require('fs');
|
|
|
|
const app = express();
|
|
const PORT = 3000;
|
|
|
|
app.use(express.static('public'));
|
|
|
|
const server = app.listen(PORT, () => {
|
|
console.log(`Server running on http://localhost:${PORT}`);
|
|
console.log('Using Python SpeechRecognition with PocketSphinx for local STT');
|
|
});
|
|
|
|
const wss = new WebSocket.Server({ server });
|
|
|
|
class SpeechProcessor {
|
|
constructor() {
|
|
this.pythonProcess = null;
|
|
this.initializePythonProcess();
|
|
}
|
|
|
|
initializePythonProcess() {
|
|
try {
|
|
this.pythonProcess = spawn('python3', ['speech_processor.py'], {
|
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
});
|
|
|
|
this.pythonProcess.stderr.on('data', (data) => {
|
|
console.error('Python process error:', data.toString());
|
|
});
|
|
|
|
this.pythonProcess.on('close', (code) => {
|
|
console.log(`Python process closed with code ${code}`);
|
|
// Restart process if it dies
|
|
setTimeout(() => this.initializePythonProcess(), 1000);
|
|
});
|
|
|
|
console.log('Python speech processor initialized');
|
|
} catch (error) {
|
|
console.error('Failed to initialize Python process:', error);
|
|
}
|
|
}
|
|
|
|
async processAudio(audioBuffer) {
|
|
return new Promise((resolve, reject) => {
|
|
if (!this.pythonProcess) {
|
|
reject(new Error('Python process not available'));
|
|
return;
|
|
}
|
|
|
|
// Send audio data length first
|
|
const lengthBuffer = Buffer.allocUnsafe(4);
|
|
lengthBuffer.writeUInt32BE(audioBuffer.length, 0);
|
|
this.pythonProcess.stdin.write(lengthBuffer);
|
|
|
|
// Send audio data
|
|
this.pythonProcess.stdin.write(audioBuffer);
|
|
|
|
// Read response
|
|
let responseLength = null;
|
|
let responseData = Buffer.alloc(0);
|
|
let expecting = 'length';
|
|
|
|
const onData = (data) => {
|
|
responseData = Buffer.concat([responseData, data]);
|
|
|
|
if (expecting === 'length' && responseData.length >= 4) {
|
|
responseLength = responseData.readUInt32BE(0);
|
|
responseData = responseData.slice(4);
|
|
expecting = 'data';
|
|
}
|
|
|
|
if (expecting === 'data' && responseData.length >= responseLength) {
|
|
const jsonData = responseData.slice(0, responseLength);
|
|
this.pythonProcess.stdout.removeListener('data', onData);
|
|
|
|
try {
|
|
const result = JSON.parse(jsonData.toString());
|
|
resolve(result);
|
|
} catch (error) {
|
|
reject(error);
|
|
}
|
|
}
|
|
};
|
|
|
|
this.pythonProcess.stdout.on('data', onData);
|
|
|
|
// Timeout after 10 seconds
|
|
setTimeout(() => {
|
|
this.pythonProcess.stdout.removeListener('data', onData);
|
|
reject(new Error('Speech processing timeout'));
|
|
}, 10000);
|
|
});
|
|
}
|
|
}
|
|
|
|
const speechProcessor = new SpeechProcessor();
|
|
|
|
wss.on('connection', (ws) => {
|
|
console.log('Client connected');
|
|
|
|
ws.on('message', async (data) => {
|
|
try {
|
|
if (Buffer.isBuffer(data)) {
|
|
// Raw audio data received
|
|
const result = await speechProcessor.processAudio(data);
|
|
|
|
if (result.success && result.text) {
|
|
ws.send(JSON.stringify({
|
|
type: 'transcription',
|
|
text: result.text
|
|
}));
|
|
console.log('Transcription:', result.text);
|
|
} else if (!result.success) {
|
|
console.error('STT Error:', result.error);
|
|
ws.send(JSON.stringify({
|
|
type: 'error',
|
|
message: result.error
|
|
}));
|
|
}
|
|
} else {
|
|
// JSON message received
|
|
const message = JSON.parse(data);
|
|
console.log('Received message:', message);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error processing message:', error);
|
|
ws.send(JSON.stringify({
|
|
type: 'error',
|
|
message: 'Error processing audio'
|
|
}));
|
|
}
|
|
});
|
|
|
|
ws.on('close', () => {
|
|
console.log('Client disconnected');
|
|
});
|
|
|
|
ws.on('error', (error) => {
|
|
console.error('WebSocket error:', error);
|
|
});
|
|
}); |