import { Router } from 'express'; import multer from 'multer'; import { execFile } from 'child_process'; import { tmpdir } from 'os'; import { join } from 'path'; import { promisify } from 'util'; import { writeFile, unlink } from 'fs'; import { raspiService } from '../services/raspi.service.js'; const execFileAsync = promisify(execFile); const writeFileAsync = promisify(writeFile); const unlinkAsync = promisify(unlink); const router = Router(); const upload = multer({ storage: multer.memoryStorage() }); router.get('/incoming', async (_req, res) => { try { const result = await raspiService.listIncomingAudio(); res.json(result); } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Unknown error'; res.status(500).json({ error: `List incoming failed: ${message}` }); } }); router.post('/lock/:filename', async (req, res) => { try { const { filename } = req.params; const result = await raspiService.lockAudio({ filename }); res.json(result); } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Unknown error'; res.status(500).json({ error: `Lock audio failed: ${message}` }); } }); router.post('/unlock/:filename', async (req, res) => { try { const { filename } = req.params; const result = await raspiService.unlockAudio({ filename }); res.json(result); } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Unknown error'; res.status(500).json({ error: `Unlock audio failed: ${message}` }); } }); router.post('/cancel/:filename', async (req, res) => { try { const { filename } = req.params; const result = await raspiService.cancelAudio({ filename }); res.json(result); } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Unknown error'; res.status(500).json({ error: `Cancel audio failed: ${message}` }); } }); router.post('/process/:filename', async (req, res) => { try { const { filename } = req.params; const result = await raspiService.processAudio({ filename }); res.json(result); } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Unknown error'; res.status(500).json({ error: `Process audio failed: ${message}` }); } }); const whisperModel = process.env.WHISPER_MODEL ?? 'base'; const whisperLanguage = process.env.WHISPER_LANGUAGE ?? 'ca'; router.post('/upload', upload.single('file'), async (req, res) => { let tmpFile: string | undefined; try { if (!req.file) { return res.status(400).json({ error: 'No audio file provided' }); } const ext = req.file.originalname.split('.').pop()?.toLowerCase() || 'wav'; tmpFile = join(tmpdir(), `quibot-audio-${Date.now()}.${ext}`); await writeFileAsync(tmpFile, req.file.buffer); console.log(`[whisper] Model: ${whisperModel}, Language: ${whisperLanguage}, File: ${tmpFile}`); const { stdout, stderr } = await execFileAsync('whisper', [ tmpFile, '--model', whisperModel, '--language', whisperLanguage, '--output_format', 'txt', ], { maxBuffer: 50 * 1024 * 1024 }); if (stderr) { console.log(`[whisper] stderr: ${stderr}`); } const transcription = stdout.trim(); res.json({ transcription, originalFilename: req.file.originalname, }); } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Unknown error'; res.status(500).json({ error: `Audio transcription failed: ${message}` }); } finally { if (tmpFile) { try { await unlinkAsync(tmpFile); } catch { // ignore cleanup errors } } } }); export default router;