""" TTS Server for ComfyUI Text-to-Speech Node GeekatPlay Studio GitHub: https://github.com/GeekatPlayStudio YouTube: @geekatplay | @geekatplay-ru (Russian) Patreon: https://patreon.com/geekatplay Flask server providing text-to-speech via Microsoft Edge TTS with pyttsx3 fallback. Runs on localhost:5002 Endpoints: - POST /tts: Generate speech audio from text - GET /status: Server health check """ from flask import Flask, request, jsonify import os import uuid import asyncio import pyttsx3 import time import threading app = Flask(__name__) OUTPUT_DIR = "outputs" os.makedirs(OUTPUT_DIR, exist_ok=True) def cleanup_old_files(): """Background task to delete temporary audio files older than 1 hour.""" while True: try: now = time.time() cutoff = 3600 # 1 hour count = 0 for f in os.listdir(OUTPUT_DIR): f_path = os.path.join(OUTPUT_DIR, f) if os.path.isfile(f_path): if now - os.path.getmtime(f_path) > cutoff: try: os.remove(f_path) count += 1 except OSError: pass if count > 0: print(f"[GeekatPlay Cleanup] Removed {count} old temporary files.") except Exception as e: print(f"[GeekatPlay Cleanup] Error: {e}") time.sleep(1800) # Check every 30 minutes @app.route("/tts", methods=["POST"]) def tts_route(): """Generate speech audio from text using Edge TTS or pyttsx3 fallback.""" data = request.get_json() text = data.get("text", "").strip() voice = data.get("voice", "en-US-AriaNeural").strip() rate = data.get("rate", 180) volume = data.get("volume", 1.0) if not text: return jsonify({"error": "No text provided"}), 400 # Generate unique filename wav_name = f"{uuid.uuid4().hex}.wav" wav_path = os.path.abspath(os.path.join(OUTPUT_DIR, wav_name)) # Try Edge TTS first (high-quality neural voices) try: from edge_tts import Communicate # Convert rate/volume to Edge TTS format # Rate: +0% format. Base 180. rate_diff = int(((rate - 180) / 180) * 100) rate_str = f"{rate_diff:+d}%" # Volume: +0% format. Base 1.0. vol_diff = int((volume - 1.0) * 100) vol_str = f"{vol_diff:+d}%" communicate = Communicate(text, voice, rate=rate_str, volume=vol_str) asyncio.run(communicate.save(wav_path)) if os.path.exists(wav_path) and os.path.getsize(wav_path) > 0: return jsonify({ "ok": True, "file": wav_name, "path": wav_path }) else: return jsonify({"error": "Failed to generate audio"}), 500 except Exception as e: # Fallback to pyttsx3 if Edge TTS fails try: engine = pyttsx3.init() engine.setProperty('rate', rate) engine.setProperty('volume', volume) # Save speech to file engine.save_to_file(text, wav_path) engine.runAndWait() # Return success response if os.path.exists(wav_path) and os.path.getsize(wav_path) > 0: return jsonify({ "ok": True, "file": wav_name, "path": wav_path }) else: return jsonify({"error": "Failed to generate audio"}), 500 except Exception as e2: return jsonify({"error": f"TTS failed: {str(e2)}"}), 500 @app.route("/status", methods=["GET"]) def status(): """Health check endpoint to verify server is running.""" return jsonify({"status": "running", "provider": "GeekatPlay Studio"}) if __name__ == "__main__": print("\n" + "="*50) print(" GeekatPlay Studio TTS Server") print(" Powered by Edge TTS & pyttsx3") print("="*50 + "\n") # Start cleanup thread cleanup_thread = threading.Thread(target=cleanup_old_files, daemon=True) cleanup_thread.start() print("[GeekatPlay] Cleanup service started (1 hour retention)") print("[GeekatPlay] Server listening on http://127.0.0.1:5002") app.run(host="127.0.0.1", port=5002)