/** * POST /api/coach/speak * Empfängt text → FreeTTS (Microsoft Neural Voices, kostenlos) → gibt base64 Audio zurück */ export default defineEventHandler(async (event) => { await requireUser(event); const body = await readBody(event); const { text } = body as { text: string }; if (!text?.trim()) { throw createError({ statusCode: 400, message: "text fehlt" }); } // Max 4096 Zeichen const trimmed = text.slice(0, 4096); try { // FreeTTS API - free, no key required const response = await fetch("https://freetts.org/api/tts", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ text: trimmed, voice: "de-DE-KatjaNeural", speed: 1.0, output: "mp3", }), }); const responseText = await response.text(); console.log("[speak] FreeTTS response status:", response.status); console.log("[speak] FreeTTS response body:", responseText); if (!response.ok) { console.error("[speak] FreeTTS error:", response.status, responseText); throw createError({ statusCode: 502, message: `TTS fehlgeschlagen: ${responseText}` }); } // FreeTTS returns a file_id to download const result = JSON.parse(responseText); if (!result.file_id) { console.error("[speak] FreeTTS no file_id:", result); throw createError({ statusCode: 502, message: "TTS fehlgeschlagen: no file_id" }); } // Download the audio file from correct endpoint console.log("[speak] Downloading audio file:", result.file_id); const audioResponse = await fetch(`https://freetts.org/api/audio/${result.file_id}`); if (!audioResponse.ok) { console.error("[speak] Audio download failed:", audioResponse.status); throw createError({ statusCode: 502, message: "TTS fehlgeschlagen: download failed" }); } const audioBuffer = await audioResponse.arrayBuffer(); const base64 = Buffer.from(audioBuffer).toString("base64"); return { audio: `data:audio/mp3;base64,${base64}` }; } catch (err: any) { console.error("[speak] TTS error:", err?.message || err); throw createError({ statusCode: 502, message: err?.message || "TTS fehlgeschlagen", }); } });