import fs from 'node:fs'; import path from 'node:path'; import cors from 'cors'; import express from 'express'; import multer from 'multer'; import { nodewhisper } from 'nodejs-whisper'; const app = express(); const port = Number(process.env.STT_PORT ?? 3334); const host = process.env.STT_HOST ?? '0.0.0.0'; const uploadDir = path.resolve(process.cwd(), '.stt-uploads'); const modelRootPath = path.resolve(process.cwd(), '.whisper-models'); const modelName = process.env.STT_MODEL ?? 'tiny.en'; fs.mkdirSync(uploadDir, { recursive: true }); fs.mkdirSync(modelRootPath, { recursive: true }); const upload = multer({ dest: uploadDir }); app.use(cors()); app.get('/health', (_request, response) => { response.json({ ok: true, model: modelName }); }); app.post('/transcribe', upload.single('audio'), async (request, response) => { if (!request.file) { response.status(400).json({ error: 'Missing audio file' }); return; } try { const text = await nodewhisper(request.file.path, { modelName, autoDownloadModelName: modelName, modelRootPath, removeWavFileAfterTranscription: true, withCuda: false, logger: quietLogger, whisperOptions: { outputInText: false, outputInSrt: false, outputInVtt: false, outputInCsv: false, outputInJson: false, outputInJsonFull: false, outputInLrc: false, outputInWords: false, translateToEnglish: false, splitOnWord: true, noGpu: true, }, }); response.json({ text: cleanTranscript(text) }); } catch (error) { response.status(500).json({ error: error instanceof Error ? error.message : String(error) }); } finally { safeUnlink(request.file.path); } }); app.listen(port, host, () => { console.log(`STT server listening on http://${host}:${port}`); console.log(`Whisper model: ${modelName}`); }); const quietLogger = { debug: () => undefined, info: () => undefined, log: () => undefined, warn: console.warn, error: console.error, }; function cleanTranscript(value) { return String(value) .replace(/\[[^\]]*\]/g, ' ') .replace(/\([^)]*\)/g, ' ') .replace(/\s+/g, ' ') .trim(); } function safeUnlink(filePath) { if (filePath && fs.existsSync(filePath)) { fs.unlinkSync(filePath); } }