feat: lots 501-504 — voice-clone endpoint + 19 backends

POST /generate/voice-clone — XTTS-v2 voice cloning (6s ref audio)
19 AI Bridge backends total

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
kxkm
2026-03-22 01:27:44 +01:00
parent 2102d82901
commit 5b62c9f12f
+58 -1
View File
@@ -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";