135 lines
4.3 KiB
Python
135 lines
4.3 KiB
Python
"""
|
|
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) |