diff --git a/ttstoy/webui/app.py b/ttstoy/webui/app.py index 5bc7d6a..2854945 100644 --- a/ttstoy/webui/app.py +++ b/ttstoy/webui/app.py @@ -590,7 +590,7 @@ def _resolve_guild_id() -> str: @app.route("/api/mc/login", methods=["POST"]) def api_mc_login(): - """Minecraft mod login endpoint. Accepts JSON {"token": "..."} and returns user info + voices.""" + """Minecraft mod login endpoint. Accepts JSON {"token": "..."} and returns user info + personal voices.""" data = request.get_json(force=True) if request.is_json else {} token = data.get("token", "").strip() if not token: @@ -603,11 +603,12 @@ def api_mc_login(): user_id = entry["user_id"] ucfg = get_user_config(user_id) voices = ucfg.get("chatterbox_voices", []) + # Only return voices that belong to this user (prefixed with their user_id) result = [] for v in voices: - parts = v.split("_", 1) - display = parts[1].rsplit(".", 1)[0] if len(parts) > 1 else v.rsplit(".", 1)[0] - result.append({"filename": v, "display": display}) + if v.startswith(f"{user_id}_"): + display = v.split("_", 1)[1].rsplit(".", 1)[0] + result.append({"filename": v, "display": display}) return jsonify({ "user_id": user_id, @@ -616,6 +617,78 @@ def api_mc_login(): }) +@app.route("/api/mc/voices", methods=["GET"]) +def api_mc_voices(): + """List all predefined voices from the Chatterbox server.""" + try: + chatterbox_url = get_chatterbox_url() + r = requests.get(f"{chatterbox_url}/get_predefined_voices", timeout=10) + r.raise_for_status() + return jsonify(r.json()) + except Exception as e: + return jsonify({"error": str(e)}), 502 + + +@app.route("/api/mc/tts", methods=["POST"]) +def api_mc_tts(): + """Generate TTS audio. Returns WAV bytes. + JSON body: {"text": "...", "mode": "chatterbox|dectalk|morshu", "voice": "Emily.wav"} + """ + data = request.get_json(force=True) if request.is_json else {} + text = (data.get("text") or "").strip() + if not text: + return jsonify({"error": "Missing text"}), 400 + + mode = data.get("mode", "chatterbox") + voice = data.get("voice", "Emily.wav") + + try: + if mode == "chatterbox": + chatterbox_url = get_chatterbox_url() + payload = { + "text": text, + "voice_mode": "predefined", + "predefined_voice_id": voice, + "output_format": "wav", + "split_text": False, + } + r = requests.post(f"{chatterbox_url}/tts", json=payload, timeout=120) + r.raise_for_status() + return r.content, 200, {"Content-Type": "audio/wav"} + + elif mode == "clone": + chatterbox_url = get_chatterbox_url() + payload = { + "text": text, + "voice_mode": "clone", + "reference_audio_filename": voice, + "output_format": "wav", + "split_text": False, + } + r = requests.post(f"{chatterbox_url}/tts", json=payload, timeout=120) + r.raise_for_status() + return r.content, 200, {"Content-Type": "audio/wav"} + + elif mode == "dectalk": + dectalk_url = get_dectalk_url() + r = requests.get(f"{dectalk_url}/say", params={"text": text}, timeout=30) + r.raise_for_status() + return r.content, 200, {"Content-Type": "audio/wav"} + + elif mode == "morshu": + r = requests.get("http://127.0.0.1:3002/say", params={"text": text}, timeout=60) + r.raise_for_status() + return r.content, 200, {"Content-Type": "audio/wav"} + + else: + return jsonify({"error": f"Unknown mode: {mode}"}), 400 + + except requests.exceptions.ConnectionError: + return jsonify({"error": f"{mode} server not reachable"}), 502 + except Exception as e: + return jsonify({"error": str(e)}), 500 + + # --------------------------------------------------------------------------- # Routes — Chatterbox voice management # ---------------------------------------------------------------------------