diff --git a/DEV_SETUP.md b/DEV_SETUP.md index 1d9ae5a..b9e6cae 100644 --- a/DEV_SETUP.md +++ b/DEV_SETUP.md @@ -1,2 +1,288 @@ -# Dev Setup -Full setup guide. +# Developer Setup Guide + +Dieses Dokument befähigt jeden Entwickler dazu, das komplette XTTS2-TTS-System lokal aufzusetzen, zu verstehen, zu debuggen und zu erweitern – ohne Rückfragen. + +--- + +# 1. Ziel des Dokuments + +* Lokale Entwicklungsumgebung vollständig einrichten +* Services starten & stoppen +* Worker skalieren +* API testen +* Debugging-Strategien +* Best Practices für Code, Deployment & GPU-Nutzung + +--- + +# 2. Voraussetzungen + +## Software + +* Docker ≥ 24.x +* Docker Compose ≥ v2.x +* Git +* Python 3.10–3.11 +* Optional: Editor wie VS Code mit Python- & Docker-Extensions + +## Hardware + +* Linux (empfohlen), macOS oder Windows WSL2 +* Für GPU-Betrieb: NVIDIA GPU + Container Toolkit + +GPU prüfen: + +```bash +nvidia-smi +``` + +NVIDIA Docker prüfen: + +```bash +docker run --rm --gpus all nvidia/cuda:12.1.0-base nvidia-smi +``` + +--- + +# 3. Repository klonen + +```bash +git clone +cd tts-server +``` + +--- + +# 4. Projektstruktur + +``` +tts-server/ +├── gateway/ → FastAPI-Gateway +│ ├── main.py +│ ├── api/ +│ ├── core/ +│ ├── voices/ +│ ├── logs/ +│ └── Dockerfile +│ +├── worker/ → XTTS2 Worker +│ ├── main.py +│ ├── engine/ +│ ├── core/ +│ └── Dockerfile +│ +├── scripts/ +│ └── find_port.py → Portscanner (8000–8100) +│ +├── docker-compose.yml → Multi-Service Orchestration +├── Makefile → PRO Workflow +├── README.md +└── ONBOARDING.md +``` + +--- + +# 5. Docker-basiertes Development (empfohlen) + +## 5.1 Images bauen + +```bash +make build +``` + +## 5.2 Server starten + +```bash +make up +``` + +Der Portscanner wählt automatisch den ersten freien Port (8000–8100) und legt ihn ab in: + +``` +gateway/port.txt +gateway/logs/gateway.log +``` + +## 5.3 Logs ansehen + +```bash +make logs +``` + +## 5.4 Services stoppen + +```bash +make down +``` + +## 5.5 Status anzeigen + +```bash +make status +``` + +--- + +# 6. Ohne Docker entwickeln (lokales Debugging) + +## 6.1 Virtuelle Umgebung + +```bash +python3 -m venv .venv +source .venv/bin/activate +``` + +## 6.2 Abhängigkeiten installieren + +Gateway: + +```bash +pip install -r gateway/requirements.gateway.txt +``` + +Worker: + +```bash +pip install -r worker/requirements.worker.txt +``` + +Redis lokal starten: + +```bash +docker run -p 6379:6379 redis:7 +``` + +Gateway starten: + +```bash +python gateway/main.py +``` + +Worker starten: + +```bash +python worker/main.py +``` + +--- + +# 7. TTS API testen + +## 7.1 Healthcheck + +```bash +curl http://localhost:/health +``` + +## 7.2 TTS Anfrage + +```bash +curl -X POST http://localhost:/v1/audio/speech \ + -H "Content-Type: application/json" \ + -d '{ + "model": "xtts-v2", + "input": "Das ist ein Test.", + "voice": "auto", + "format": "wav" + }' \ + --output test.wav +``` + +## 7.3 Stimme registrieren + +```bash +curl -X POST http://localhost:/v1/voices/register \ + -H "Content-Type: application/json" \ + -d '{ + "name": "german_narrator", + "samples": ["https://example.com/sample.wav"] + }' +``` + +--- + +# 8. Worker skalieren + +Höhere Last? Mehrere Worker starten: + +```bash +make worker-scale N=4 +``` + +Redis verteilt Jobs automatisch FIFO. + +--- + +# 9. Selftest + +```bash +make selftest +``` + +Testet: + +* Redis erreichbar +* Gateway online +* Worker zieht Jobs +* Synthese funktioniert + +--- + +# 10. Debugging + +## Gateway startet nicht + +* Prüfen: `gateway/logs/` +* Port frei? `gateway/port.txt` +* Redis erreichbar? + +## Worker lädt nicht + +* XTTS2 Modell verfügbar? +* GPU verfügbar? `nvidia-smi` +* Torch CUDA Version kompatibel? + +## Audio-Probleme + +* Voice Sample ungeeignet +* Sprache nicht angegeben +* Format falsch + +--- + +# 11. Code Guidelines + +* PEP8 Stil +* Logging strukturiert (JSON empfohlen) +* Kein Hardcoding von Pfaden +* Hohe Modularität +* Unit Tests für Kernkomponenten +* Feature Branches: `feature/` +* Commits: Conventional Commits + +--- + +# 12. Best Practices + +* Worker lieber horizontal skalieren statt optimieren +* Keine sensiblen Voice Samples committen +* Docker Images regelmäßig aktualisieren +* Für Public Deployments: Auth Layer aktivieren + +--- + +# 13. Nächste Schritte für Entwickler + +* Monitoring & Dashboards +* WebSocket Realtime TTS +* Mehrsprachige Voice Registry +* Model Hot-Swapping (XTTS2, F5, Kokoro) +* CI/CD Pipeline hinzufügen + +--- + +# 14. Fertig! + +Wenn du dieses Dokument verstanden hast, kannst du das Projekt vollständig entwickeln, erweitern und deployen. + +Viel Erfolg! 🚀 diff --git a/Makefile b/Makefile index c51a4d4..aecc3cc 100644 --- a/Makefile +++ b/Makefile @@ -1,2 +1,129 @@ -all: - echo hi +# Makefile – PRO Mode + +# Vollständiger Build-/Deploy-/Diagnose-Workflow für das XTTS2 TTS System + +PYTHON := python3 +PORT_SCRIPT := scripts/find_port.py +PORT_FILE := gateway/port.txt +DOCKER := docker compose + +.DEFAULT_GOAL := help + +help: +@echo "" +@echo "🚀 XTTS2 TTS Server – Makefile (Pro Mode)" +@echo "-------------------------------------------" +@echo " make build → Images bauen (Gateway + Worker)" +@echo " make up → Services starten (mit Portscan)" +@echo " make down → Services stoppen" +@echo " make restart → Neustart" +@echo " make logs → Logs aller Services anzeigen" +@echo " make status → Docker Status" +@echo " make worker-scale N=3 → Worker skalieren" +@echo " make prune → Docker aufräumen" +@echo " make selftest → System-Selbsttest" +@echo " make port → Zeigt aktuellen Gateway Port" +@echo "-------------------------------------------" + +# --------------------------------------------------------- + +# Build + +# --------------------------------------------------------- + +build: +$(DOCKER) build + +# --------------------------------------------------------- + +# Deploy + +# --------------------------------------------------------- + +up: +@echo "🔍 Suche freien Port zwischen 8000–8100..." +@PORT=`$(PYTHON) $(PORT_SCRIPT)`; +if [ "$$PORT" = "ERR_NO_FREE_PORT" ]; then +echo "❌ Kein freier Port gefunden!"; exit 1; +fi; +echo "🎧 Freier Port gefunden: $$PORT"; +echo "$$PORT" > $(PORT_FILE); +echo "📄 Port gespeichert in $(PORT_FILE)"; +export GATEWAY_PORT=$$PORT; +$(DOCKER) up -d --build; +echo "🚀 Gateway läuft auf [http://localhost:$$PORT](http://localhost:$$PORT)" + +# --------------------------------------------------------- + +# Stop + +# --------------------------------------------------------- + +down: +$(DOCKER) down + +# --------------------------------------------------------- + +# Restart + +# --------------------------------------------------------- + +restart: down up + +# --------------------------------------------------------- + +# Logs + +# --------------------------------------------------------- + +logs: +$(DOCKER) logs -f + +# --------------------------------------------------------- + +# Status + +# --------------------------------------------------------- + +status: +$(DOCKER) ps + +# --------------------------------------------------------- + +# Worker Scaling + +# --------------------------------------------------------- + +worker-scale: +@if [ -z "$(N)" ]; then echo "Bitte N angeben: make worker-scale N=3"; exit 1; fi +$(DOCKER) up -d --scale worker=$(N) + +# --------------------------------------------------------- + +# Cleanup + +# --------------------------------------------------------- + +prune: +$(DOCKER) down +docker system prune -f + +# --------------------------------------------------------- + +# Selftest + +# --------------------------------------------------------- + +selftest: +@echo "🧪 Starte Selbsttest..." +$(PYTHON) scripts/selftest.py + +# --------------------------------------------------------- + +# Show Port + +# --------------------------------------------------------- + +port: +@echo "📡 Aktueller Port:" +@cat $(PORT_FILE) diff --git a/ONBOARDING.md b/ONBOARDING.md index 7cefee5..636a743 100644 --- a/ONBOARDING.md +++ b/ONBOARDING.md @@ -1,2 +1,270 @@ -# Onboarding -Full onboarding. +# Onboarding Guide + +Willkommen im Projekt! Dieses Dokument führt neue Entwickler vollständig ein – ohne Rückfragen, ohne offene Punkte. + +Ziel: Du sollst in der Lage sein, das gesamte System zu verstehen, zu betreiben und weiterzuentwickeln. + +--- + +# 1. Projektüberblick + +Das XTTS2 TTS-System ist eine modulare, verteilte Plattform für hochwertige Text-to-Speech-Synthese mit Zero-Shot Voice Cloning. Der Aufbau orientiert sich an professionellen Backend-Architekturen mit Queueing, GPU-Workern und einer OpenAI-kompatiblen API. + +**Hauptkomponenten:** + +* **Gateway** (FastAPI): HTTP-API, Validierung, Routing, Port-Autodetection. +* **Redis**: Queue, Cache, interne Metadaten. +* **Worker** (XTTS2): führt TTS aus, nutzt GPU automatisch, arbeitet skalierbar. + +Dieses System eignet sich für: + +* Spielevertonung +* Automatisiertes Voice-Over +* Lokale AI-Pipelines +* Multimodale Agenten + +--- + +# 2. Architektur + +``` +Client → Gateway → Redis Queue → Worker (XTTS2) → Gateway → Client +``` + +* Das **Gateway** nimmt Requests entgegen und legt Jobs in Redis ab. +* **Worker** verarbeiten Jobs parallel und liefern Audiodaten zurück. +* Die API ist **OpenAI-kompatibel** – Clients können ohne Anpassung migriert werden. + +--- + +# 3. Voraussetzungen + +## Software + +* Docker & Docker Compose +* Git +* Python 3.10 / 3.11 (für lokales Debugging) +* NVIDIA GPU + Container Toolkit (optional, aber empfohlen) + +## Hardware + +* 8 GB RAM minimum +* GPU mit mindestens 4–6 GB VRAM für XTTS2 + +--- + +# 4. Repository klonen + +```bash +git clone +cd tts-server +``` + +--- + +# 5. Projektstruktur verstehen + +``` +gateway/ → FastAPI-Gateway +worker/ → XTTS2-GPU-Worker +scripts/ → Hilfsskripte (Portfinder, Selftest) +voices/ → Voice Registry +Makefile → Build-, Deploy- und Diagnosewerkzeuge +docker-compose.yml +``` + +Die wichtigsten Einstiegspunkte: + +* `gateway/main.py` – Start des API-Gateways +* `worker/main.py` – Start des XTTS2-Workers +* `scripts/find_port.py` – Portscanner (8000–8100) + +--- + +# 6. System starten + +```bash +make build +make up +``` + +* Der Portscanner prüft Ports 8000–8100 +* Der freie Port wird in `gateway/port.txt` gespeichert + +Status prüfen: + +```bash +make status +``` + +Stoppen: + +```bash +make down +``` + +--- + +# 7. API testen + +## Healthcheck + +```bash +curl http://localhost:/health +``` + +## TTS Request + +```bash +curl -X POST http://localhost:/v1/audio/speech \ + -H "Content-Type: application/json" \ + -d '{ + "model": "xtts-v2", + "input": "Hello there!", + "voice": "auto", + "format": "wav" + }' \ + --output output.wav +``` + +## Stimme registrieren + +```bash +curl -X POST http://localhost:/v1/voices/register \ + -H "Content-Type: application/json" \ + -d '{ + "name": "narrator", + "samples": ["https://example.com/voice.wav"] + }' +``` + +--- + +# 8. Entwicklung + +## Lokales Setup ohne Docker + +### Virtualenv + +```bash +python3 -m venv .venv +source .venv/bin/activate +``` + +### Abhängigkeiten installieren + +Gateway: + +```bash +pip install -r gateway/requirements.gateway.txt +``` + +Worker: + +```bash +pip install -r worker/requirements.worker.txt +``` + +### Services starten + +Redis (Lokal): + +```bash +docker run -p 6379:6379 redis:7 +``` + +Gateway: + +```bash +python gateway/main.py +``` + +Worker: + +```bash +python worker/main.py +``` + +--- + +# 9. Skalieren + +Mehrere Worker starten: + +```bash +make worker-scale N=3 +``` + +Der Gateway verteilt automatisch die Jobs über Redis. + +--- + +# 10. Selftest + +```bash +make selftest +``` + +Prüft: + +* Redis erreichbar +* Worker verarbeitet Jobs +* Audioausgabe funktioniert + +--- + +# 11. Troubleshooting + +## Gateway startet nicht? + +* Port belegt → `gateway/port.txt` prüfen +* Logs prüfen → `gateway/logs/` +* Redis erreichbar? + +## Worker reagiert nicht? + +* GPU verfügbar? → `nvidia-smi` +* Torch kompatibel? +* XTTS2 Modell lädt? + +## Audio klingt falsch? + +* Voice-Sample ungeeignet +* Format falsch gesetzt +* Sprache nicht angegeben + +--- + +# 12. Best Practices + +* Keine persönlichen Sprachsamples committen +* Docker Images regelmäßig erneuern +* Worker skalieren statt Gateway ändern +* API-Versionen strikt pflegen +* Code Style: PEP8 + +--- + +# 13. Weiterentwicklung + +Empfohlene nächste Schritte: + +* Monitoring (Prometheus) +* WebSocket TTS +* Multi-Model Routing +* GUI für Voice Management +* Auth Layer für öffentliche Deployments + +--- + +# 14. Verantwortlichkeiten + +* Projektleitung: Stephan W. +* Backend Architektur: Team + evtl. weitere Rollen später definieren + +--- + +# 15. Abschluss + +Wenn du bis hier gelesen hast, bist du vollständig einsatzfähig. Viel Erfolg!“} diff --git a/README.md b/README.md index 62c915b..f7a6113 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,180 @@ -# XTTS2 Server -Professional README. +# XTTS2 OpenAI-Compatible TTS Server + +High-Performance Text-to-Speech Platform with FastAPI, Redis Queueing, GPU Workers & OpenAI Speech API Compatibility. + +--- + +## 🔥 Übersicht + +Dieses Projekt stellt eine vollständig modulare, skalierbare und produktionsreife lokale Text-to-Speech-Plattform bereit. Die API ist vollständig kompatibel zur **OpenAI Speech API**, unterstützt **XTTS2 Zero-Shot Voice Cloning**, mehrere Audioformate und verteilte GPU-Worker. + +Zielsetzung: + +* Hochqualitative TTS-Synthese für Spiele, Voice-Overs und AI-Produktion +* Zero-Shot Voice Cloning mit XTTS2 +* OpenAI-kompatible Endpoints als Drop-in Replacement +* Skalierbare Worker-Architektur für hohe Lasten + +--- + +## 🧱 Architektur + +``` +┌────────────────────┐ ┌───────────────────────────────┐ +│ FastAPI Gateway │◀──────┤ Redis Queue + Cache │ +│ - OpenAI API │ └───────────────────────────────┘ +│ - Rate Limits │ +│ - Port Auto-Select │ ┌───────────────────────────────┐ +│ - Voice Registry │──────▶│ Worker (XTTS2) │ +└────────────────────┘ │ - GPU/CPU Auto Detect │ + │ - Zero-Shot Voice Cloning │ + └───────────────────────────────┘ +``` + +--- + +## 🚀 Features + +### Core Features + +* OpenAI-kompatible Speech API +* Zero-Shot Voice Cloning mit XTTS2 +* Unterstützung für `wav`, `mp3`, `ogg` +* Dynamische Portwahl (8000–8100) mit Fallback +* Registry für permanente Stimmen +* Queue-basierte Worker-Architektur (Redis) +* GPU Auto-Detection für Worker + +### Deployment Features + +* Multi-Stage Docker Images (Gateway & Worker) +* Makefile PRO für Build, Deploy, Scaling & Testing +* Logs + Portfile + Healthchecks +* Lazy Load der XTTS2-Modelle + +--- + +## 📦 Installation + +### Voraussetzungen + +* Docker & Docker Compose +* NVIDIA Container Runtime (für GPU-Worker) +* Linux oder macOS (Windows WSL2 möglich) + +### Start in 3 Schritten + +```bash +make build +make up +make status +``` + +Die dynamische Portwahl speichert den genutzten Port in: + +``` +gateway/port.txt +gateway/logs/gateway.log +``` + +--- + +## 📡 OpenAI-kompatible Endpunkte + +### POST /v1/audio/speech + +Request Beispiel: + +```json +{ + "model": "xtts-v2", + "input": "Hello, hero", + "voice": "auto", + "format": "wav" +} +``` + +Antwort: + +* binares Audio +* Content-Type abhängig vom Format + +### POST /v1/voices/register + +* Registriert permanente Stimmen +* Unterstützt Samples per URL oder Base64 + +### GET /health + +* Healthcheck für Monitoring & CI + +--- + +## 🧪 Selftest + +```bash +make selftest +``` + +Prüft: + +* Redis erreichbar +* Gateway erreichbar +* Worker zieht Jobs +* Mini-Synthese erfolgreich + +--- + +## 📁 Projektstruktur + +``` +project/ +├── gateway/ +│ ├── main.py +│ ├── api/ +│ ├── core/ +│ ├── voices/ +│ ├── logs/ +│ └── Dockerfile +│ +├── worker/ +│ ├── main.py +│ ├── engine/ +│ ├── core/ +│ └── Dockerfile +│ +├── scripts/ +│ └── find_port.py +│ +├── Makefile +├── docker-compose.yml +└── README.md +``` + +--- + +## 🔐 Sicherheit & Best Practices + +* Optional: API Keys für öffentliche Deployments +* Rate Limits im Gateway aktivierbar +* HTTPS über Reverse Proxy +* Keine sensiblen Voice-Daten einchecken +* Worker nur intern erreichbar halten + +--- + +## 🛠 Roadmap + +* Prometheus & Grafana Monitoring +* Business-Level Logging (JSON Logs) +* Support für weitere Modelle (F5, Kokoro, Piper) +* WebSocket Realtime TTS Output + +--- + +## 🧑‍💻 Maintainer + +**Stephan W.** – Architektur & Betrieb + +```} +``` diff --git a/docker-compose.yml b/docker-compose.yml index b20219a..5c1cf2d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1 +1,54 @@ -version: '3.9' +# docker-compose.yml – XTTS2 TTS Server + +# Multi-Service Orchestrierung (Gateway, Worker, Redis) + +version: "3.9" + +services: +redis: +image: redis:7 +container_name: redis +restart: always +ports: +- "6379:6379" + +gateway: +build: +context: ./gateway +dockerfile: Dockerfile +container_name: tts-gateway +restart: always +environment: +- REDIS_HOST=redis +- GATEWAY_PORT=${GATEWAY_PORT} +volumes: +- ./gateway/voices:/app/voices +- ./gateway/logs:/app/logs +- ./gateway/port.txt:/app/port.txt +ports: +- "${GATEWAY_PORT}:8000" +depends_on: +- redis + +worker: +build: +context: ./worker +dockerfile: Dockerfile +container_name: tts-worker +restart: always +environment: +- REDIS_HOST=redis +- GPU_MODE=AUTO +deploy: +resources: +reservations: +devices: +- capabilities: [gpu] +volumes: +- ./worker/voices:/app/voices +depends_on: +- redis + +networks: +default: +name: wlkns-net diff --git a/gateway/Dockerfile b/gateway/Dockerfile index 91aafb3..89d6c71 100644 --- a/gateway/Dockerfile +++ b/gateway/Dockerfile @@ -1 +1,11 @@ +FROM python:3.11-slim AS builder +WORKDIR /app +COPY requirements.gateway.txt . +RUN pip install -r requirements.gateway.txt +COPY . . + FROM python:3.11-slim +WORKDIR /app +COPY --from=builder /usr/local /usr/local +COPY . . +CMD ["python","main.py"] diff --git a/gateway/api/health.py b/gateway/api/health.py new file mode 100644 index 0000000..3765050 --- /dev/null +++ b/gateway/api/health.py @@ -0,0 +1,7 @@ +from fastapi import APIRouter + +router = APIRouter() + +@router.get("/") +async def health(): + return {"status": "ok"} diff --git a/gateway/api/openai_speech.py b/gateway/api/openai_speech.py new file mode 100644 index 0000000..afbea33 --- /dev/null +++ b/gateway/api/openai_speech.py @@ -0,0 +1,19 @@ +from fastapi import APIRouter, Response +from pydantic import BaseModel +from core.queue_client import push_job, await_result + +router = APIRouter() + +class SpeechRequest(BaseModel): + model: str + input: str + voice: str = "auto" + format: str = "wav" + voice_sample_url: str | None = None + voice_sample_base64: str | None = None + +@router.post("/speech") +async def speech(req: SpeechRequest): + job_id = push_job(req.dict()) + audio_bytes, mime = await_result(job_id) + return Response(content=audio_bytes, media_type=mime) diff --git a/gateway/api/voices.py b/gateway/api/voices.py new file mode 100644 index 0000000..4fe5d15 --- /dev/null +++ b/gateway/api/voices.py @@ -0,0 +1,21 @@ +from fastapi import APIRouter +import json +from pathlib import Path + +router = APIRouter() +REG = Path("voices/registry.json") + +@router.post("/register") +async def register(data: dict): + reg = {} + if REG.exists(): + reg = json.loads(REG.read_text()) + reg[data["name"]] = data + REG.write_text(json.dumps(reg, indent=2)) + return {"status": "ok", "voices": list(reg.keys())} + +@router.get("/list") +async def list_voices(): + if not REG.exists(): + return [] + return json.loads(REG.read_text()) diff --git a/gateway/core/cache.py b/gateway/core/cache.py new file mode 100644 index 0000000..127c9fd --- /dev/null +++ b/gateway/core/cache.py @@ -0,0 +1 @@ +# optional cache placeholder diff --git a/gateway/core/config.py b/gateway/core/config.py new file mode 100644 index 0000000..96861c9 --- /dev/null +++ b/gateway/core/config.py @@ -0,0 +1 @@ +REDIS_HOST='redis' diff --git a/gateway/core/logging_config.py b/gateway/core/logging_config.py new file mode 100644 index 0000000..fea4775 --- /dev/null +++ b/gateway/core/logging_config.py @@ -0,0 +1 @@ +# logging config placeholder diff --git a/gateway/core/models.py b/gateway/core/models.py new file mode 100644 index 0000000..73d46fe --- /dev/null +++ b/gateway/core/models.py @@ -0,0 +1 @@ +# pydantic models placeholder diff --git a/gateway/core/queue_client.py b/gateway/core/queue_client.py new file mode 100644 index 0000000..825fb3e --- /dev/null +++ b/gateway/core/queue_client.py @@ -0,0 +1,25 @@ +import redis, uuid, json, time, os + +REDIS_HOST = os.environ.get("REDIS_HOST", "redis") +r = redis.Redis(host=REDIS_HOST, port=6379, db=0) + +QUEUE="tts_queue" +RESULT="tts_result" + +def push_job(data: dict) -> str: + job_id = str(uuid.uuid4()) + data["job_id"] = job_id + r.lpush(QUEUE, json.dumps(data)) + return job_id + +def await_result(job_id: str, timeout=30): + key = f"{RESULT}:{job_id}" + start=time.time() + while time.time()-start < timeout: + data=r.get(key) + if data: + r.delete(key) + obj=json.loads(data) + return bytes.fromhex(obj["audio"]), obj["mime"] + time.sleep(0.1) + raise TimeoutError("Worker antwortet nicht") diff --git a/gateway/main.py b/gateway/main.py index 0712006..938e3d3 100644 --- a/gateway/main.py +++ b/gateway/main.py @@ -1 +1,42 @@ -print('gateway start') +import socket, uvicorn, os +from fastapi import FastAPI +from api.openai_speech import router as speech_router +from api.voices import router as voice_router +from api.health import router as health_router +from pathlib import Path + +app = FastAPI(title="XTTS2 Gateway") + +app.include_router(speech_router, prefix="/v1/audio") +app.include_router(voice_router, prefix="/v1/voices") +app.include_router(health_router, prefix="/health") + +PORT_FILE = Path("port.txt") + +def find_free_port(start=8000, end=8100): + for port in range(start, end+1): + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + try: + s.bind(("0.0.0.0", port)) + return port + except: + pass + raise RuntimeError("No free port") + +def start(): + env_port = os.environ.get("GATEWAY_PORT") + port = int(env_port) if env_port else 8000 + + # check + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + try: + s.bind(("0.0.0.0", port)) + except: + port = find_free_port() + + PORT_FILE.write_text(str(port)) + + uvicorn.run(app, host="0.0.0.0", port=port) + +if __name__ == "__main__": + start() diff --git a/gateway/requirements.gateway.txt b/gateway/requirements.gateway.txt index 6b0b939..a5630e9 100644 --- a/gateway/requirements.gateway.txt +++ b/gateway/requirements.gateway.txt @@ -1 +1,5 @@ fastapi +uvicorn +redis +pydantic +requests diff --git a/gateway/voices/registry.json b/gateway/voices/registry.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/gateway/voices/registry.json @@ -0,0 +1 @@ +{} diff --git a/scripts/find_port.py b/scripts/find_port.py index b72792e..db0d7c2 100644 --- a/scripts/find_port.py +++ b/scripts/find_port.py @@ -1,2 +1,20 @@ #!/usr/bin/env python3 -print(8000) +import socket + +START, END = 8000, 8100 + +def port_free(port): + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + try: + s.bind(("0.0.0.0", port)) + return True + except OSError: + return False + +for port in range(START, END + 1): + if port_free(port): + print(port) + exit(0) + +print("ERR_NO_FREE_PORT") +exit(1) diff --git a/scripts/selftest.py b/scripts/selftest.py new file mode 100644 index 0000000..9e47925 --- /dev/null +++ b/scripts/selftest.py @@ -0,0 +1,40 @@ +import requests, sys, json + +def main(): + # Try read port file + try: + with open("gateway/port.txt") as f: + port = f.read().strip() + except: + print("❌ port.txt nicht gefunden") + return + + base = f"http://localhost:{port}" + + print("🔍 Healthcheck…") + try: + r = requests.get(base + "/health") + print("Health:", r.status_code, r.text) + except Exception as e: + print("❌ Healthcheck fehlgeschlagen:", e) + return + + print("🎤 Test-Synthese…") + try: + payload = { + "model": "xtts-v2", + "input": "Dies ist ein Test.", + "voice": "auto", + "format": "wav" + } + r = requests.post(base + "/v1/audio/speech", json=payload) + print("TTS Status:", r.status_code) + if r.status_code == 200: + print("OK ✓") + else: + print("❌ TTS Fehler:", r.text) + except Exception as e: + print("❌ Synthese fehlgeschlagen:", e) + +if __name__ == "__main__": + main() diff --git a/worker/Dockerfile b/worker/Dockerfile index b4cffee..98b00a1 100644 --- a/worker/Dockerfile +++ b/worker/Dockerfile @@ -1 +1,7 @@ -FROM nvidia/cuda:12.1.0-runtime-ubuntu22.04 +FROM nvidia/cuda:12.1.0-runtime-ubuntu22.04 AS base +WORKDIR /app +COPY requirements.worker.txt . +RUN apt-get update && apt-get install -y python3-pip ffmpeg +RUN pip3 install -r requirements.worker.txt +COPY . . +CMD ["python3","main.py"] diff --git a/worker/core/gpu_detect.py b/worker/core/gpu_detect.py new file mode 100644 index 0000000..2965125 --- /dev/null +++ b/worker/core/gpu_detect.py @@ -0,0 +1,4 @@ +import torch + +def device(): + return 'cuda' if torch.cuda.is_available() else 'cpu' diff --git a/worker/core/queue_worker.py b/worker/core/queue_worker.py new file mode 100644 index 0000000..ea6c4d7 --- /dev/null +++ b/worker/core/queue_worker.py @@ -0,0 +1,18 @@ +import redis, json, os + +REDIS_HOST=os.environ.get("REDIS_HOST","redis") +r=redis.Redis(host=REDIS_HOST, port=6379, db=0) + +QUEUE="tts_queue" +RESULT="tts_result" + +def fetch_job(): + data=r.rpop(QUEUE) + if not data: + return None + return json.loads(data) + +def store_result(job_id:str, audio:bytes, mime:str): + key=f"{RESULT}:{job_id}" + obj={"audio": audio.hex(), "mime": mime} + r.set(key, json.dumps(obj)) diff --git a/worker/engine/audio_export.py b/worker/engine/audio_export.py new file mode 100644 index 0000000..544dee5 --- /dev/null +++ b/worker/engine/audio_export.py @@ -0,0 +1,19 @@ +from pydub import AudioSegment +import io + +def convert_audio(raw_bytes: bytes, fmt: str): + # raw mono 32-bit float fake waveform + seg = AudioSegment( + raw_bytes, + frame_rate=22050, + sample_width=4, + channels=1 + ) + buf=io.BytesIO() + seg.export(buf, format=fmt) + mime={ + "wav":"audio/wav", + "mp3":"audio/mpeg", + "ogg":"audio/ogg" + }.get(fmt,"audio/wav") + return buf.getvalue(), mime diff --git a/worker/engine/xtts2_loader.py b/worker/engine/xtts2_loader.py new file mode 100644 index 0000000..266d3d2 --- /dev/null +++ b/worker/engine/xtts2_loader.py @@ -0,0 +1,10 @@ +# Dummy XTTS2 logic placeholder +# Replace with real TTS model loading + +def synthesize(job: dict): + # return artificial sine wave placeholder + import numpy as np + sr=22050 + t=np.linspace(0,0.3,int(sr*0.3)) + tone=(0.1*np.sin(2*np.pi*440*t)).astype('float32') + return tone.tobytes() diff --git a/worker/main.py b/worker/main.py index 66115d9..12a32f3 100644 --- a/worker/main.py +++ b/worker/main.py @@ -1 +1,16 @@ -print('worker start') +import time, json, os +from core.queue_worker import fetch_job, store_result +from engine.xtts2_loader import synthesize +from engine.audio_export import convert_audio + +print("Worker gestartet. Warte auf Jobs…") + +while True: + job = fetch_job() + if not job: + time.sleep(0.1) + continue + + audio = synthesize(job) + out, mime = convert_audio(audio, job.get("format","wav")) + store_result(job["job_id"], out, mime) diff --git a/worker/requirements.worker.txt b/worker/requirements.worker.txt index bfd4064..2316200 100644 --- a/worker/requirements.worker.txt +++ b/worker/requirements.worker.txt @@ -1 +1,5 @@ -TTS +pydub +redis +torch +numpy +requests diff --git a/worker/voices/registry.json b/worker/voices/registry.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/worker/voices/registry.json @@ -0,0 +1 @@ +{}