'No text']); exit; } // Cap at 400 chars to protect free-tier quota $text = mb_substr($text, 0, 400); if (!defined('ELEVENLABS_API_KEY') || !ELEVENLABS_API_KEY) { http_response_code(503); echo json_encode(['error' => 'ElevenLabs not configured']); exit; } $payload = json_encode([ 'text' => $text, 'model_id' => ELEVENLABS_MODEL, 'voice_settings' => ['stability' => 0.45, 'similarity_boost' => 0.80, 'style' => 0.10], ]); $ch = curl_init('https://api.elevenlabs.io/v1/text-to-speech/' . ELEVENLABS_VOICE_ID); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ 'xi-api-key: ' . ELEVENLABS_API_KEY, 'Content-Type: application/json', 'Accept: audio/mpeg', ], CURLOPT_POSTFIELDS => $payload, CURLOPT_TIMEOUT => 20, CURLOPT_CONNECTTIMEOUT => 5, ]); $audio = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($code === 200 && $audio) { header('Content-Type: audio/mpeg'); header('Cache-Control: no-store'); echo $audio; } else { http_response_code(502); echo json_encode(['error' => 'ElevenLabs error', 'code' => $code]); }