diff --git a/ai-bridge.js b/ai-bridge.js index 9f7cf1f1..107ef917 100644 --- a/ai-bridge.js +++ b/ai-bridge.js @@ -71,7 +71,7 @@ const server = http.createServer(async (req, res) => { if (url.pathname === "/health") { res.writeHead(200, {"Content-Type":"application/json"}); - res.end(JSON.stringify({ok:true,service:"ai-bridge",backends:["tts","musicgen","noise","ollama","drums","bass","pad","choir","fx","kokoro-tts","ace-step","demucs","drone","grain","glitch","circus","honk","sound-design"]})); + res.end(JSON.stringify({ok:true,service:"ai-bridge",backends:["tts","musicgen","noise","ollama","drums","bass","pad","choir","fx","kokoro-tts","ace-step","demucs","drone","grain","glitch","circus","honk","sound-design","voice-clone"]})); return; } if (url.pathname === "/generate/music" && req.method === "POST") { @@ -1049,6 +1049,63 @@ const server = http.createServer(async (req, res) => { } + + // ── POST /generate/voice-clone ── + // XTTS-v2 voice cloning: 6s reference audio → clone voice + if (url.pathname === "/generate/voice-clone" && req.method === "POST") { + const body = JSON.parse(await readBody(req)); + const { text = "Bonjour", reference_audio, language = "fr" } = body; + if (!text) { res.writeHead(400, {"Content-Type":"application/json"}); res.end(JSON.stringify({error:"text required"})); return; } + + const out = tmpWav(); + try { + // Use Coqui TTS XTTS-v2 via Python + const pyScript = ` +import sys, json +from TTS.api import TTS +tts = TTS("tts_models/multilingual/multi-dataset/xtts_v2", gpu=True) +ref = sys.argv[1] if len(sys.argv) > 1 and sys.argv[1] != "none" else None +text = sys.argv[2] +lang = sys.argv[3] +out = sys.argv[4] +if ref: + tts.tts_to_file(text=text, file_path=out, speaker_wav=ref, language=lang) +else: + tts.tts_to_file(text=text, file_path=out, language=lang) +print(json.dumps({"ok": True})) +`; + const pyFile = `/tmp/xtts-gen-${Date.now()}.py`; + fs.writeFileSync(pyFile, pyScript); + + let refFile = "none"; + if (reference_audio) { + refFile = `/tmp/xtts-ref-${Date.now()}.wav`; + fs.writeFileSync(refFile, Buffer.from(reference_audio, "base64")); + } + + execSync(`/home/kxkm/venv/bin/python ${pyFile} "${refFile}" "${text.replace(/"/g, '\\"').slice(0, 500)}" "${language}" "${out}"`, { + timeout: 120000, + env: { ...process.env, CUDA_VISIBLE_DEVICES: "0" } + }); + + cleanup(pyFile); + if (refFile !== "none") cleanup(refFile); + + if (fs.existsSync(out)) { + sendWav(res, out); + } else { + res.writeHead(500, {"Content-Type":"application/json"}); + res.end(JSON.stringify({error: "XTTS generation failed — no output"})); + } + } catch(e) { + cleanup(out); + res.writeHead(500, {"Content-Type":"application/json"}); + res.end(JSON.stringify({error: e.message})); + } + return; + } + + // === Auto-Mastering (Matchering) === if (url.pathname === "/master" && req.method === "POST") { const tmp_target = "/tmp/ai-master-target-" + Date.now() + ".wav";