import { NextRequest, NextResponse } from "next/server" import { execSync } from "child_process" import { existsSync, mkdirSync } from "fs" import { createHash } from "crypto" import { readFile, mkdir } from "fs/promises" import { join } from "path" const CACHE_DIR = join(process.cwd(), "public", "tts-cache") async function ensureCacheDir() { if (!existsSync(CACHE_DIR)) { await mkdir(CACHE_DIR, { recursive: true }) } } function getCachePath(text: string): string { const hash = createHash("md5").update(text).digest("hex") return join(CACHE_DIR, `${hash}.wav`) } export async function GET(request: NextRequest) { const text = request.nextUrl.searchParams.get("text") if (!text || text.length > 200) { return NextResponse.json({ error: "text param required (max 200 chars)" }, { status: 400 }) } await ensureCacheDir() const cachePath = getCachePath(text) if (!existsSync(cachePath)) { try { execSync( `espeak-ng -v es-mx -s 140 -p 60 "${text.replace(/"/g, '\\"')}" -w "${cachePath}"`, { timeout: 10000 }, ) } catch { return NextResponse.json({ error: "TTS generation failed" }, { status: 500 }) } } const audioBuffer = await readFile(cachePath) return new NextResponse(audioBuffer, { headers: { "Content-Type": "audio/wav", "Content-Length": audioBuffer.length.toString(), "Cache-Control": "public, max-age=31536000, immutable", }, }) }