You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

61 lines
1.9 KiB

/**
* Standalone HTTP server: same contract as legacy aitherboard `POST /api/piper-tts`
* (JSON `{ text, speed?, voice? }` → `audio/wav`), forwarding to Wyoming Piper over TCP.
*/
import http from 'node:http'
import { handlePiperTtsPost } from './server'
const PORT = Number(process.env.PORT || 9876)
function cors(res: http.ServerResponse) {
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS')
res.setHeader('Access-Control-Allow-Headers', 'Content-Type')
}
const server = http.createServer(async (req, res) => {
cors(res)
if (req.method === 'OPTIONS') {
res.writeHead(204).end()
return
}
const rawPath = req.url?.split('?')[0] || ''
const path = rawPath.replace(/\/$/, '') || '/'
if (req.method !== 'POST' || path !== '/api/piper-tts') {
res.writeHead(404, { 'Content-Type': 'text/plain' }).end('Not found')
return
}
const chunks: Buffer[] = []
for await (const c of req) {
chunks.push(c as Buffer)
}
const bodyBuf = Buffer.concat(chunks)
const request = new Request(`http://127.0.0.1:${PORT}${rawPath}`, {
method: 'POST',
headers: { 'content-type': req.headers['content-type'] || 'application/json' },
body: bodyBuf
})
try {
const out = await handlePiperTtsPost(request)
res.statusCode = out.status
out.headers.forEach((value, key) => {
res.setHeader(key, value)
})
const ab = await out.arrayBuffer()
res.end(Buffer.from(ab))
} catch (e) {
const msg = e instanceof Error ? e.message : String(e)
console.error('[piper-tts-proxy]', msg)
res.statusCode = 500
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ error: msg }))
}
})
server.listen(PORT, '0.0.0.0', () => {
const host = process.env.PIPER_TTS_HOST || '(default)'
console.log(`[piper-tts-proxy] http://0.0.0.0:${PORT}/api/piper-tts → Wyoming ${host}:${process.env.PIPER_TTS_PORT || '10200'}`)
})