c3
parent
c07ba54ed1
commit
127758a930
150
server.js
150
server.js
|
@ -1,7 +1,6 @@
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const WebSocket = require('ws');
|
const WebSocket = require('ws');
|
||||||
const { spawn } = require('child_process');
|
const { spawn } = require('child_process');
|
||||||
const fs = require('fs');
|
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = 3000;
|
const PORT = 3000;
|
||||||
|
@ -17,53 +16,45 @@ const wss = new WebSocket.Server({ server });
|
||||||
class SpeechProcessor {
|
class SpeechProcessor {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.pythonProcess = null;
|
this.pythonProcess = null;
|
||||||
this.requestMap = new Map();
|
this.activeRequests = new Map();
|
||||||
this.requestCounter = 0;
|
this.requestCounter = 0;
|
||||||
this.initializePythonProcess();
|
this.initializePythonProcess();
|
||||||
|
this.buffer = Buffer.alloc(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
initializePythonProcess() {
|
initializePythonProcess() {
|
||||||
try {
|
this.pythonProcess = spawn('python3', ['speech_processor.py']);
|
||||||
this.pythonProcess = spawn('python3', ['speech_processor.py'], {
|
|
||||||
stdio: ['pipe', 'pipe', 'pipe']
|
|
||||||
});
|
|
||||||
|
|
||||||
this.pythonProcess.stderr.on('data', (data) => {
|
this.pythonProcess.stderr.on('data', (data) => {
|
||||||
console.error('Python STDERR:', data.toString());
|
console.error('Python STDERR:', data.toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
this.pythonProcess.on('close', (code) => {
|
this.pythonProcess.on('close', (code) => {
|
||||||
console.log(`Python process exited with code ${code}`);
|
console.log(`Python process exited with code ${code}`);
|
||||||
this.requestMap.clear();
|
this.activeRequests.clear();
|
||||||
setTimeout(() => this.initializePythonProcess(), 1000);
|
setTimeout(() => this.initializePythonProcess(), 1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
let buffer = Buffer.alloc(0);
|
this.pythonProcess.stdout.on('data', (data) => {
|
||||||
this.pythonProcess.stdout.on('data', (data) => {
|
this.buffer = Buffer.concat([this.buffer, data]);
|
||||||
buffer = Buffer.concat([buffer, data]);
|
this.processBuffer();
|
||||||
this.processBuffer(buffer);
|
});
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Python processor initialized');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to start Python:', error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
processBuffer(buffer) {
|
processBuffer() {
|
||||||
while (buffer.length >= 8) {
|
while (this.buffer.length >= 8) {
|
||||||
const length = buffer.readUInt32BE(0);
|
const length = this.buffer.readUInt32BE(0);
|
||||||
const requestId = buffer.readUInt32BE(4);
|
const requestId = this.buffer.readUInt32BE(4);
|
||||||
|
|
||||||
if (buffer.length >= 8 + length) {
|
if (this.buffer.length >= 8 + length) {
|
||||||
const message = buffer.slice(8, 8 + length);
|
const message = this.buffer.slice(8, 8 + length);
|
||||||
buffer = buffer.slice(8 + length);
|
this.buffer = this.buffer.slice(8 + length);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = JSON.parse(message.toString());
|
const result = JSON.parse(message.toString());
|
||||||
if (this.requestMap.has(requestId)) {
|
if (this.activeRequests.has(requestId)) {
|
||||||
const { resolve } = this.requestMap.get(requestId);
|
const { resolve } = this.activeRequests.get(requestId);
|
||||||
this.requestMap.delete(requestId);
|
this.activeRequests.delete(requestId);
|
||||||
resolve(result);
|
resolve(result);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -73,7 +64,6 @@ class SpeechProcessor {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return buffer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async processAudio(audioBuffer) {
|
async processAudio(audioBuffer) {
|
||||||
|
@ -84,7 +74,7 @@ class SpeechProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
const requestId = this.requestCounter++;
|
const requestId = this.requestCounter++;
|
||||||
this.requestMap.set(requestId, { resolve, reject });
|
this.activeRequests.set(requestId, { resolve, reject });
|
||||||
|
|
||||||
const lengthBuffer = Buffer.alloc(4);
|
const lengthBuffer = Buffer.alloc(4);
|
||||||
lengthBuffer.writeUInt32BE(audioBuffer.length, 0);
|
lengthBuffer.writeUInt32BE(audioBuffer.length, 0);
|
||||||
|
@ -97,8 +87,8 @@ class SpeechProcessor {
|
||||||
this.pythonProcess.stdin.write(audioBuffer);
|
this.pythonProcess.stdin.write(audioBuffer);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.requestMap.has(requestId)) {
|
if (this.activeRequests.has(requestId)) {
|
||||||
this.requestMap.delete(requestId);
|
this.activeRequests.delete(requestId);
|
||||||
reject(new Error('Processing timeout'));
|
reject(new Error('Processing timeout'));
|
||||||
}
|
}
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
@ -112,49 +102,61 @@ wss.on('connection', (ws) => {
|
||||||
console.log('Client connected');
|
console.log('Client connected');
|
||||||
|
|
||||||
let lastFinalText = '';
|
let lastFinalText = '';
|
||||||
let lastPartialUpdate = 0;
|
let lastPartialText = '';
|
||||||
let partialText = '';
|
let partialTextTimeout = null;
|
||||||
|
|
||||||
ws.on('message', async (message) => {
|
ws.on('message', async (message) => {
|
||||||
|
if (!Buffer.isBuffer(message)) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (Buffer.isBuffer(message)) {
|
const result = await speechProcessor.processAudio(message);
|
||||||
const result = await speechProcessor.processAudio(message);
|
|
||||||
|
if (!result.success) {
|
||||||
if (result.success) {
|
console.error('Processing error:', result.error);
|
||||||
if (result.is_final) {
|
return;
|
||||||
if (result.text && result.text !== lastFinalText) {
|
}
|
||||||
lastFinalText = result.text;
|
|
||||||
ws.send(JSON.stringify({
|
// Handle final results
|
||||||
type: 'transcription',
|
if (result.is_final && result.text && result.text !== lastFinalText) {
|
||||||
text: result.text,
|
lastFinalText = result.text;
|
||||||
is_final: true
|
ws.send(JSON.stringify({
|
||||||
}));
|
type: 'transcription',
|
||||||
console.log('Final:', result.text);
|
text: result.text,
|
||||||
partialText = '';
|
is_final: true
|
||||||
}
|
}));
|
||||||
} else {
|
console.log('Final:', result.text);
|
||||||
// Only send partial updates every 300ms
|
lastPartialText = '';
|
||||||
const now = Date.now();
|
if (partialTextTimeout) {
|
||||||
if (result.text && (now - lastPartialUpdate > 300 || !result.text.startsWith(partialText))) {
|
clearTimeout(partialTextTimeout);
|
||||||
partialText = result.text;
|
partialTextTimeout = null;
|
||||||
lastPartialUpdate = now;
|
|
||||||
ws.send(JSON.stringify({
|
|
||||||
type: 'partial_transcription',
|
|
||||||
text: result.text,
|
|
||||||
is_final: false
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error('Processing error:', result.error);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// Handle partial results with debouncing
|
||||||
|
else if (!result.is_final && result.text && result.text !== lastPartialText) {
|
||||||
|
lastPartialText = result.text;
|
||||||
|
|
||||||
|
if (partialTextTimeout) {
|
||||||
|
clearTimeout(partialTextTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
partialTextTimeout = setTimeout(() => {
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
type: 'partial_transcription',
|
||||||
|
text: result.text,
|
||||||
|
is_final: false
|
||||||
|
}));
|
||||||
|
partialTextTimeout = null;
|
||||||
|
}, 300); // 300ms debounce
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('WebSocket error:', error);
|
console.error('Processing error:', error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ws.on('close', () => {
|
ws.on('close', () => {
|
||||||
console.log('Client disconnected');
|
console.log('Client disconnected');
|
||||||
|
if (partialTextTimeout) {
|
||||||
|
clearTimeout(partialTextTimeout);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
Loading…
Reference in New Issue