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:
+58
-1
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user