From 09e5f99b8642ae4caf20c18c1714d2da29083170 Mon Sep 17 00:00:00 2001 From: teahubxyz Date: Wed, 21 Oct 2026 02:14:25 +0000 Subject: [PATCH] =?UTF-8?q?inference:=20POST=20/v1/speech=20=E2=80=94=20Ve?= =?UTF-8?q?nice=20TTS=20(model=20in=20TTS=5FMODELS)=20for=20the=20DJ's=20s?= =?UTF-8?q?poken=20breaks;=20counts=20as=20a=20render?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01K6897PWcUEwHiqZMcVnNF6 --- .github/time/cloudflare/inference.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/time/cloudflare/inference.js b/.github/time/cloudflare/inference.js index 6689233..d0dde1a 100644 --- a/.github/time/cloudflare/inference.js +++ b/.github/time/cloudflare/inference.js @@ -7,6 +7,7 @@ // POST /v1/chat/completions model in CHAT_MODELS, no streaming, max_tokens <= MAX_TOKENS // POST /v1/audio/quote | queue | retrieve | complete model in AUDIO_MODELS, duration_seconds <= AUDIO_SECONDS // POST /v1/image/generate model in IMAGE_MODELS +// POST /v1/speech Venice TTS: { input, model?, voice?, speed?, response_format? }, model in TTS_MODELS // POST /v1/render one call, audio back: { prompt, lyrics?, duration?, bpm?, key?, timeSignature?, seed?, quality?, reference? } // reference: a data: URL of audio the new track should take after (Runware only; up to 8 MB) // Runware ACE-Step when RUNWARE_API_KEY is bound (quality "xl" picks XL Turbo), @@ -176,6 +177,16 @@ async function api(req, env, url, h) { if (audio[1] === "retrieve") body.delete_media_on_completion = true; // nothing of theirs stays at Venice return withCors(await forward("/audio/" + audio[1], body, env), h); } + if (path === "/v1/speech") { + const m = model || "tts-kokoro"; + if (!list(env.TTS_MODELS || "tts-kokoro,tts-orpheus,tts-qwen3-1-7b").includes(m)) return json({ error: `model not allowed here: ${m.slice(0, 40)}` }, 403, h); + if (!body.input || String(body.input).length < 2) return json({ error: "input is empty" }, 400, h); + const q = { model: m, input: String(body.input).slice(0, 4096), voice: String(body.voice || "af_sky").slice(0, 64), response_format: /^(mp3|wav|flac|opus|aac)$/.test(body.response_format) ? body.response_format : "mp3", + speed: Math.max(0.5, Math.min(1.5, +body.speed || 1)) }; + const no = await spend(env, app, "render", person, num(env.RENDER_DAILY, 20), num(env.RENDER_TOTAL, 400)); + if (no) return json({ error: no }, 429, h); + return withCors(await forward("/audio/speech", q, env), h); + } if (path === "/v1/image/generate") { if (!list(env.IMAGE_MODELS).includes(model)) return json({ error: `model not allowed here: ${model.slice(0, 40)}` }, 403, h); body.safe_mode = true;