lollms-client 1.5.6__py3-none-any.whl → 1.7.10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lollms_client/__init__.py +1 -1
- lollms_client/llm_bindings/azure_openai/__init__.py +2 -2
- lollms_client/llm_bindings/claude/__init__.py +125 -34
- lollms_client/llm_bindings/gemini/__init__.py +261 -159
- lollms_client/llm_bindings/grok/__init__.py +52 -14
- lollms_client/llm_bindings/groq/__init__.py +2 -2
- lollms_client/llm_bindings/hugging_face_inference_api/__init__.py +2 -2
- lollms_client/llm_bindings/litellm/__init__.py +1 -1
- lollms_client/llm_bindings/llamacpp/__init__.py +18 -11
- lollms_client/llm_bindings/lollms/__init__.py +76 -21
- lollms_client/llm_bindings/lollms_webui/__init__.py +1 -1
- lollms_client/llm_bindings/mistral/__init__.py +2 -2
- lollms_client/llm_bindings/novita_ai/__init__.py +142 -6
- lollms_client/llm_bindings/ollama/__init__.py +307 -89
- lollms_client/llm_bindings/open_router/__init__.py +2 -2
- lollms_client/llm_bindings/openai/__init__.py +81 -20
- lollms_client/llm_bindings/openllm/__init__.py +362 -506
- lollms_client/llm_bindings/openwebui/__init__.py +333 -171
- lollms_client/llm_bindings/perplexity/__init__.py +2 -2
- lollms_client/llm_bindings/pythonllamacpp/__init__.py +3 -3
- lollms_client/llm_bindings/tensor_rt/__init__.py +1 -1
- lollms_client/llm_bindings/transformers/__init__.py +428 -632
- lollms_client/llm_bindings/vllm/__init__.py +1 -1
- lollms_client/lollms_agentic.py +4 -2
- lollms_client/lollms_base_binding.py +61 -0
- lollms_client/lollms_core.py +512 -1890
- lollms_client/lollms_discussion.py +25 -11
- lollms_client/lollms_llm_binding.py +112 -261
- lollms_client/lollms_mcp_binding.py +34 -75
- lollms_client/lollms_stt_binding.py +85 -52
- lollms_client/lollms_tti_binding.py +23 -37
- lollms_client/lollms_ttm_binding.py +24 -42
- lollms_client/lollms_tts_binding.py +28 -17
- lollms_client/lollms_ttv_binding.py +24 -42
- lollms_client/lollms_types.py +4 -2
- lollms_client/stt_bindings/whisper/__init__.py +108 -23
- lollms_client/stt_bindings/whispercpp/__init__.py +7 -1
- lollms_client/tti_bindings/diffusers/__init__.py +418 -810
- lollms_client/tti_bindings/diffusers/server/main.py +1051 -0
- lollms_client/tti_bindings/gemini/__init__.py +182 -239
- lollms_client/tti_bindings/leonardo_ai/__init__.py +6 -3
- lollms_client/tti_bindings/lollms/__init__.py +4 -1
- lollms_client/tti_bindings/novita_ai/__init__.py +5 -2
- lollms_client/tti_bindings/openai/__init__.py +10 -11
- lollms_client/tti_bindings/stability_ai/__init__.py +5 -3
- lollms_client/ttm_bindings/audiocraft/__init__.py +7 -12
- lollms_client/ttm_bindings/beatoven_ai/__init__.py +7 -3
- lollms_client/ttm_bindings/lollms/__init__.py +4 -17
- lollms_client/ttm_bindings/replicate/__init__.py +7 -4
- lollms_client/ttm_bindings/stability_ai/__init__.py +7 -4
- lollms_client/ttm_bindings/topmediai/__init__.py +6 -3
- lollms_client/tts_bindings/bark/__init__.py +7 -10
- lollms_client/tts_bindings/lollms/__init__.py +6 -1
- lollms_client/tts_bindings/piper_tts/__init__.py +8 -11
- lollms_client/tts_bindings/xtts/__init__.py +157 -74
- lollms_client/tts_bindings/xtts/server/main.py +241 -280
- {lollms_client-1.5.6.dist-info → lollms_client-1.7.10.dist-info}/METADATA +113 -5
- lollms_client-1.7.10.dist-info/RECORD +89 -0
- lollms_client-1.5.6.dist-info/RECORD +0 -87
- {lollms_client-1.5.6.dist-info → lollms_client-1.7.10.dist-info}/WHEEL +0 -0
- {lollms_client-1.5.6.dist-info → lollms_client-1.7.10.dist-info}/licenses/LICENSE +0 -0
- {lollms_client-1.5.6.dist-info → lollms_client-1.7.10.dist-info}/top_level.txt +0 -0
|
@@ -1,314 +1,275 @@
|
|
|
1
|
-
import uvicorn
|
|
2
|
-
from fastapi import FastAPI, APIRouter, HTTPException
|
|
3
|
-
from pydantic import BaseModel
|
|
4
|
-
import argparse
|
|
5
|
-
import sys
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
import asyncio
|
|
8
|
-
import traceback
|
|
9
|
-
import os
|
|
10
|
-
from typing import Optional, List
|
|
11
|
-
import io
|
|
12
|
-
import wave
|
|
13
|
-
import numpy as np
|
|
14
|
-
import tempfile
|
|
15
|
-
|
|
16
|
-
# --- XTTS Implementation ---
|
|
17
1
|
try:
|
|
18
|
-
|
|
19
|
-
import
|
|
20
|
-
import
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
2
|
+
import uvicorn
|
|
3
|
+
from fastapi import FastAPI, APIRouter, HTTPException
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import asyncio
|
|
9
|
+
import traceback
|
|
10
|
+
import os
|
|
11
|
+
from typing import Optional, List
|
|
12
|
+
import io
|
|
13
|
+
import wave
|
|
14
|
+
import numpy as np
|
|
15
|
+
import tempfile
|
|
29
16
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
print(f"Server: Traceback:\n{traceback.format_exc()}")
|
|
33
|
-
xtts_available = False
|
|
34
|
-
|
|
35
|
-
# --- API Models ---
|
|
36
|
-
class GenerationRequest(BaseModel):
|
|
37
|
-
text: str
|
|
38
|
-
voice: Optional[str] = None
|
|
39
|
-
language: Optional[str] = "en"
|
|
40
|
-
speaker_wav: Optional[str] = None
|
|
17
|
+
# Use ascii_colors for logging
|
|
18
|
+
from ascii_colors import ASCIIColors
|
|
41
19
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
self.available_models = ["xtts_v2"]
|
|
20
|
+
# --- XTTS Implementation ---
|
|
21
|
+
try:
|
|
22
|
+
ASCIIColors.info("Server: Loading XTTS dependencies...")
|
|
23
|
+
import torch
|
|
24
|
+
from TTS.api import TTS
|
|
25
|
+
ASCIIColors.green("Server: XTTS dependencies loaded successfully")
|
|
49
26
|
|
|
50
|
-
#
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
self.model =
|
|
73
|
-
|
|
74
|
-
self.model_loaded = True
|
|
75
|
-
print("Server: XTTS model loaded successfully")
|
|
76
|
-
|
|
77
|
-
except Exception as e:
|
|
78
|
-
print(f"Server: Error loading XTTS model: {e}")
|
|
79
|
-
print(f"Server: Traceback:\n{traceback.format_exc()}")
|
|
27
|
+
# Check for CUDA availability
|
|
28
|
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
|
29
|
+
ASCIIColors.info(f"Server: Using device: {device}")
|
|
30
|
+
|
|
31
|
+
xtts_available = True
|
|
32
|
+
|
|
33
|
+
except Exception as e:
|
|
34
|
+
ASCIIColors.error(f"Server: Failed to load XTTS dependencies: {e}")
|
|
35
|
+
ASCIIColors.error(f"Server: Traceback:\n{traceback.format_exc()}")
|
|
36
|
+
xtts_available = False
|
|
37
|
+
|
|
38
|
+
# --- API Models ---
|
|
39
|
+
class GenerationRequest(BaseModel):
|
|
40
|
+
text: str
|
|
41
|
+
voice: Optional[str] = None
|
|
42
|
+
language: Optional[str] = "en"
|
|
43
|
+
# speaker_wav is kept for backward compatibility but voice is preferred
|
|
44
|
+
speaker_wav: Optional[str] = None
|
|
45
|
+
split_sentences: Optional[bool] = True
|
|
46
|
+
|
|
47
|
+
class XTTSServer:
|
|
48
|
+
def __init__(self):
|
|
49
|
+
self.model = None
|
|
80
50
|
self.model_loaded = False
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
self.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
"""Load and return available voices"""
|
|
87
|
-
try:
|
|
88
|
-
# Look for voice files in voices directory
|
|
89
|
-
voices_dir = Path(__file__).parent / "voices"
|
|
90
|
-
voices = []
|
|
51
|
+
self.model_loading = False # Flag to prevent concurrent loading
|
|
52
|
+
self.available_models = ["tts_models/multilingual/multi-dataset/xtts_v2"]
|
|
53
|
+
self.voices_dir = Path(__file__).parent / "voices"
|
|
54
|
+
self.voices_dir.mkdir(exist_ok=True)
|
|
55
|
+
self.available_voices = self._load_available_voices()
|
|
91
56
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if not voices:
|
|
99
|
-
voices = ["default", "female", "male"]
|
|
57
|
+
ASCIIColors.info("Server: XTTS server initialized (model will be loaded on first request)")
|
|
58
|
+
|
|
59
|
+
async def _ensure_model_loaded(self):
|
|
60
|
+
"""Ensure the XTTS model is loaded (lazy loading)"""
|
|
61
|
+
if self.model_loaded:
|
|
62
|
+
return
|
|
100
63
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
64
|
+
if self.model_loading:
|
|
65
|
+
# Another request is already loading the model, wait for it
|
|
66
|
+
while self.model_loading and not self.model_loaded:
|
|
67
|
+
await asyncio.sleep(0.1)
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
if not xtts_available:
|
|
71
|
+
raise RuntimeError("XTTS library not available. Please ensure all dependencies are installed correctly in the venv.")
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
self.model_loading = True
|
|
75
|
+
ASCIIColors.yellow("Server: Loading XTTS model for the first time (this may take a few minutes)...")
|
|
76
|
+
|
|
77
|
+
# Initialize XTTS model
|
|
78
|
+
self.model = TTS(self.available_models[0]).to(device)
|
|
79
|
+
|
|
80
|
+
self.model_loaded = True
|
|
81
|
+
ASCIIColors.green("Server: XTTS model loaded successfully")
|
|
82
|
+
|
|
83
|
+
except Exception as e:
|
|
84
|
+
ASCIIColors.error(f"Server: Error loading XTTS model: {e}")
|
|
85
|
+
ASCIIColors.error(f"Server: Traceback:\n{traceback.format_exc()}")
|
|
86
|
+
self.model_loaded = False
|
|
87
|
+
raise
|
|
88
|
+
finally:
|
|
89
|
+
self.model_loading = False
|
|
112
90
|
|
|
113
|
-
|
|
114
|
-
|
|
91
|
+
def _load_available_voices(self) -> List[str]:
|
|
92
|
+
"""Load and return available voices, ensuring 'default_voice' is always present."""
|
|
93
|
+
try:
|
|
94
|
+
self.voices_dir.mkdir(exist_ok=True)
|
|
95
|
+
|
|
96
|
+
# Scan for case-insensitive .wav and .mp3 files and get their stems
|
|
97
|
+
found_voices = {p.stem for p in self.voices_dir.glob("*.[wW][aA][vV]")}
|
|
98
|
+
found_voices.update({p.stem for p in self.voices_dir.glob("*.[mM][pP]3")})
|
|
99
|
+
|
|
100
|
+
# GUARANTEE 'default_voice' is in the list for UI consistency.
|
|
101
|
+
all_voices = {"default_voice"}.union(found_voices)
|
|
102
|
+
|
|
103
|
+
sorted_voices = sorted(list(all_voices))
|
|
104
|
+
ASCIIColors.info(f"Discovered voices: {sorted_voices}")
|
|
105
|
+
return sorted_voices
|
|
106
|
+
|
|
107
|
+
except Exception as e:
|
|
108
|
+
ASCIIColors.error(f"Server: Error scanning voices directory: {e}")
|
|
109
|
+
# If scanning fails, it's crucial to still return the default.
|
|
110
|
+
return ["default_voice"]
|
|
115
111
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
112
|
+
def _get_speaker_wav_path(self, voice_name: str) -> Optional[str]:
|
|
113
|
+
"""Find the path to a speaker wav/mp3 file from its name."""
|
|
114
|
+
if not voice_name:
|
|
115
|
+
return None
|
|
119
116
|
|
|
120
|
-
#
|
|
121
|
-
|
|
117
|
+
# Case 1: voice_name is an absolute path that exists
|
|
118
|
+
if os.path.isabs(voice_name) and os.path.exists(voice_name):
|
|
119
|
+
return voice_name
|
|
122
120
|
|
|
123
|
-
#
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
121
|
+
# Case 2: voice_name is a name in the voices directory (check for .mp3 then .wav)
|
|
122
|
+
mp3_path = self.voices_dir / f"{voice_name}.mp3"
|
|
123
|
+
if mp3_path.exists():
|
|
124
|
+
return str(mp3_path)
|
|
125
|
+
|
|
126
|
+
wav_path = self.voices_dir / f"{voice_name}.wav"
|
|
127
|
+
if wav_path.exists():
|
|
128
|
+
return str(wav_path)
|
|
127
129
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
print(f"Server: Using voice as file path: {speaker_wav_path}")
|
|
134
|
-
else:
|
|
135
|
-
# Look for voice file in voices directory
|
|
136
|
-
voices_dir = Path(__file__).parent / "voices"
|
|
137
|
-
potential_voice_path = voices_dir / f"{voice}.wav"
|
|
138
|
-
if potential_voice_path.exists():
|
|
139
|
-
speaker_wav_path = str(potential_voice_path)
|
|
140
|
-
print(f"Server: Using custom voice file: {speaker_wav_path}")
|
|
141
|
-
else:
|
|
142
|
-
print(f"Server: Voice '{voice}' not found in voices directory")
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
async def generate_audio(self, req: GenerationRequest) -> bytes:
|
|
133
|
+
"""Generate audio from text using XTTS"""
|
|
134
|
+
await self._ensure_model_loaded()
|
|
143
135
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
temp_output_path = temp_file.name
|
|
136
|
+
if not self.model_loaded or self.model is None:
|
|
137
|
+
raise RuntimeError("XTTS model failed to load or is not available.")
|
|
147
138
|
|
|
148
139
|
try:
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
140
|
+
text_to_generate = req.text
|
|
141
|
+
ASCIIColors.info(f"Server: Generating audio for: '{text_to_generate[:50]}{'...' if len(text_to_generate) > 50 else ''}'")
|
|
142
|
+
ASCIIColors.info(f"Server: Language: {req.language}, Requested Voice: {req.voice}")
|
|
143
|
+
|
|
144
|
+
# Determine which voice name to use. Priority: speaker_wav > voice > 'default_voice'
|
|
145
|
+
voice_to_find = req.speaker_wav or req.voice or "default_voice"
|
|
146
|
+
speaker_wav_path = self._get_speaker_wav_path(voice_to_find)
|
|
147
|
+
|
|
148
|
+
# If the chosen voice wasn't found and it wasn't the default, try the default as a fallback.
|
|
149
|
+
if not speaker_wav_path and voice_to_find != "default_voice":
|
|
150
|
+
ASCIIColors.warning(f"Voice '{voice_to_find}' not found. Falling back to 'default_voice'.")
|
|
151
|
+
speaker_wav_path = self._get_speaker_wav_path("default_voice")
|
|
152
|
+
|
|
153
|
+
# If still no path, it's a critical error because even the default is missing.
|
|
154
|
+
if not speaker_wav_path:
|
|
155
|
+
available = self._get_all_available_voice_files()
|
|
156
|
+
raise RuntimeError(
|
|
157
|
+
f"XTTS requires a speaker reference file, but none could be found.\n"
|
|
158
|
+
f"Attempted to use '{voice_to_find}' but it was not found, and the fallback 'default_voice.mp3' is also missing from the voices folder.\n"
|
|
159
|
+
f"Please add audio files to the '{self.voices_dir.resolve()}' directory. Available files: {available or 'None'}"
|
|
157
160
|
)
|
|
158
|
-
else:
|
|
159
|
-
print("Server: No valid speaker reference found, trying default")
|
|
160
|
-
# For XTTS without speaker reference, try to find a default
|
|
161
|
-
default_speaker = self._get_default_speaker_file()
|
|
162
|
-
if default_speaker and os.path.exists(default_speaker):
|
|
163
|
-
print(f"Server: Using default speaker: {default_speaker}")
|
|
164
|
-
self.model.tts_to_file(
|
|
165
|
-
text=text,
|
|
166
|
-
speaker_wav=default_speaker,
|
|
167
|
-
language=language,
|
|
168
|
-
file_path=temp_output_path
|
|
169
|
-
)
|
|
170
|
-
else:
|
|
171
|
-
# Create a more helpful error message
|
|
172
|
-
available_voices = self._get_all_available_voice_files()
|
|
173
|
-
error_msg = f"No speaker reference available. XTTS requires a speaker reference file.\n"
|
|
174
|
-
error_msg += f"Attempted to use: {speaker_wav_path if speaker_wav_path else 'None'}\n"
|
|
175
|
-
error_msg += f"Available voice files: {available_voices}"
|
|
176
|
-
raise RuntimeError(error_msg)
|
|
177
161
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
162
|
+
ASCIIColors.info(f"Server: Using speaker reference: {speaker_wav_path}")
|
|
163
|
+
|
|
164
|
+
# Generate audio using XTTS
|
|
165
|
+
wav_chunks = self.model.tts(
|
|
166
|
+
text=text_to_generate,
|
|
167
|
+
speaker_wav=speaker_wav_path,
|
|
168
|
+
language=req.language,
|
|
169
|
+
split_sentences=req.split_sentences
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# Combine chunks into a single audio stream
|
|
173
|
+
audio_data = np.array(wav_chunks, dtype=np.float32)
|
|
181
174
|
|
|
182
|
-
|
|
175
|
+
buffer = io.BytesIO()
|
|
176
|
+
with wave.open(buffer, 'wb') as wf:
|
|
177
|
+
wf.setnchannels(1)
|
|
178
|
+
wf.setsampwidth(2) # 16-bit
|
|
179
|
+
wf.setframerate(self.model.synthesizer.output_sample_rate)
|
|
180
|
+
wf.writeframes((audio_data * 32767).astype(np.int16).tobytes())
|
|
181
|
+
|
|
182
|
+
audio_bytes = buffer.getvalue()
|
|
183
|
+
|
|
184
|
+
ASCIIColors.green(f"Server: Generated {len(audio_bytes)} bytes of audio.")
|
|
183
185
|
return audio_bytes
|
|
184
186
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
except Exception as e:
|
|
191
|
-
print(f"Server: Error generating audio: {e}")
|
|
192
|
-
print(f"Server: Traceback:\n{traceback.format_exc()}")
|
|
193
|
-
raise
|
|
194
|
-
|
|
195
|
-
def _get_all_available_voice_files(self) -> List[str]:
|
|
196
|
-
"""Get list of all available voice files for debugging"""
|
|
197
|
-
voices_dir = Path(__file__).parent / "voices"
|
|
198
|
-
voice_files = []
|
|
187
|
+
except Exception as e:
|
|
188
|
+
ASCIIColors.error(f"Server: Error generating audio: {e}")
|
|
189
|
+
ASCIIColors.error(f"Server: Traceback:\n{traceback.format_exc()}")
|
|
190
|
+
raise
|
|
199
191
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
return voice_files
|
|
204
|
-
|
|
205
|
-
def _get_default_speaker_file(self) -> Optional[str]:
|
|
206
|
-
"""Get path to default speaker file"""
|
|
207
|
-
voices_dir = Path(__file__).parent / "voices"
|
|
192
|
+
def _get_all_available_voice_files(self) -> List[str]:
|
|
193
|
+
"""Get list of all available voice files for debugging"""
|
|
194
|
+
return [f.name for f in self.voices_dir.glob("*.*")]
|
|
208
195
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if potential_path.exists():
|
|
213
|
-
return str(potential_path)
|
|
196
|
+
def list_voices(self) -> List[str]:
|
|
197
|
+
"""Return list of available voices"""
|
|
198
|
+
return self.available_voices
|
|
214
199
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
return self.available_voices
|
|
225
|
-
|
|
226
|
-
def list_models(self) -> List[str]:
|
|
227
|
-
"""Return list of available models"""
|
|
228
|
-
return self.available_models
|
|
200
|
+
def list_models(self) -> List[str]:
|
|
201
|
+
"""Return list of available models"""
|
|
202
|
+
return self.available_models
|
|
203
|
+
|
|
204
|
+
# --- Globals ---
|
|
205
|
+
app = FastAPI(title="XTTS Server")
|
|
206
|
+
router = APIRouter()
|
|
207
|
+
xtts_server = XTTSServer()
|
|
208
|
+
model_lock = asyncio.Lock() # Ensure only one generation happens at a time on the model
|
|
229
209
|
|
|
230
|
-
# ---
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
210
|
+
# --- API Endpoints ---
|
|
211
|
+
@router.post("/generate_audio")
|
|
212
|
+
async def api_generate_audio(request: GenerationRequest):
|
|
213
|
+
async with model_lock:
|
|
214
|
+
try:
|
|
215
|
+
from fastapi.responses import Response
|
|
216
|
+
audio_bytes = await xtts_server.generate_audio(request)
|
|
217
|
+
return Response(content=audio_bytes, media_type="audio/wav")
|
|
218
|
+
except Exception as e:
|
|
219
|
+
ASCIIColors.error(f"Server: ERROR in generate_audio endpoint: {e}")
|
|
220
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
235
221
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
async def generate_audio(request: GenerationRequest):
|
|
239
|
-
async with model_lock:
|
|
222
|
+
@router.get("/list_voices")
|
|
223
|
+
async def api_list_voices():
|
|
240
224
|
try:
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
voice=request.voice,
|
|
244
|
-
language=request.language,
|
|
245
|
-
speaker_wav=request.speaker_wav
|
|
246
|
-
)
|
|
247
|
-
from fastapi.responses import Response
|
|
248
|
-
return Response(content=audio_bytes, media_type="audio/wav")
|
|
225
|
+
voices = xtts_server.list_voices()
|
|
226
|
+
return {"voices": voices}
|
|
249
227
|
except Exception as e:
|
|
250
|
-
|
|
251
|
-
print(f"Server: ERROR traceback:\n{traceback.format_exc()}")
|
|
228
|
+
ASCIIColors.error(f"Server: ERROR in list_voices endpoint: {e}")
|
|
252
229
|
raise HTTPException(status_code=500, detail=str(e))
|
|
253
230
|
|
|
254
|
-
@router.get("/
|
|
255
|
-
async def
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
print(f"Server: ERROR traceback:\n{traceback.format_exc()}")
|
|
263
|
-
raise HTTPException(status_code=500, detail=str(e))
|
|
264
|
-
|
|
265
|
-
@router.get("/list_models")
|
|
266
|
-
async def list_models():
|
|
267
|
-
try:
|
|
268
|
-
models = xtts_server.list_models()
|
|
269
|
-
print(f"Server: Returning {len(models)} models: {models}")
|
|
270
|
-
return {"models": models}
|
|
271
|
-
except Exception as e:
|
|
272
|
-
print(f"Server: ERROR in list_models endpoint: {e}")
|
|
273
|
-
print(f"Server: ERROR traceback:\n{traceback.format_exc()}")
|
|
274
|
-
raise HTTPException(status_code=500, detail=str(e))
|
|
231
|
+
@router.get("/list_models")
|
|
232
|
+
async def api_list_models():
|
|
233
|
+
try:
|
|
234
|
+
models = xtts_server.list_models()
|
|
235
|
+
return {"models": models}
|
|
236
|
+
except Exception as e:
|
|
237
|
+
ASCIIColors.error(f"Server: ERROR in list_models endpoint: {e}")
|
|
238
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
275
239
|
|
|
276
|
-
@router.get("/status")
|
|
277
|
-
async def status():
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
"device": torch.cuda.get_device_name(0) if torch.cuda.is_available() else "CPU"
|
|
285
|
-
}
|
|
240
|
+
@router.get("/status")
|
|
241
|
+
async def status():
|
|
242
|
+
return {
|
|
243
|
+
"status": "running",
|
|
244
|
+
"xtts_available": xtts_available,
|
|
245
|
+
"model_loaded": xtts_server.model_loaded,
|
|
246
|
+
"device": device if xtts_available else "N/A"
|
|
247
|
+
}
|
|
286
248
|
|
|
287
|
-
|
|
288
|
-
@router.get("/health")
|
|
289
|
-
async def health_check():
|
|
290
|
-
return {"status": "healthy", "ready": True}
|
|
249
|
+
app.include_router(router)
|
|
291
250
|
|
|
292
|
-
|
|
251
|
+
# --- Server Startup ---
|
|
252
|
+
if __name__ == '__main__':
|
|
253
|
+
parser = argparse.ArgumentParser(description="LoLLMs XTTS Server")
|
|
254
|
+
parser.add_argument("--host", type=str, default="localhost", help="Host to bind the server to.")
|
|
255
|
+
parser.add_argument("--port", type=int, default=8081, help="Port to bind the server to.")
|
|
256
|
+
|
|
257
|
+
args = parser.parse_args()
|
|
293
258
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
259
|
+
ASCIIColors.cyan("--- LoLLMs XTTS Server ---")
|
|
260
|
+
ASCIIColors.green(f"Starting server on http://{args.host}:{args.port}")
|
|
261
|
+
ASCIIColors.info(f"Voices directory: {xtts_server.voices_dir.resolve()}")
|
|
262
|
+
|
|
263
|
+
if not xtts_available:
|
|
264
|
+
ASCIIColors.red("Warning: XTTS dependencies not found. Server will run but generation will fail.")
|
|
265
|
+
else:
|
|
266
|
+
ASCIIColors.info(f"Detected device: {device}")
|
|
267
|
+
|
|
268
|
+
uvicorn.run(app, host=args.host, port=args.port)
|
|
301
269
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
# Create voices directory if it doesn't exist
|
|
310
|
-
voices_dir = Path(__file__).parent / "voices"
|
|
311
|
-
voices_dir.mkdir(exist_ok=True)
|
|
312
|
-
print(f"Server: Voices directory: {voices_dir}")
|
|
313
|
-
|
|
314
|
-
uvicorn.run(app, host=args.host, port=args.port)
|
|
270
|
+
except Exception as e:
|
|
271
|
+
# This will catch errors during initial imports
|
|
272
|
+
from ascii_colors import ASCIIColors
|
|
273
|
+
ASCIIColors.red(f"Server: CRITICAL ERROR during startup: {e}")
|
|
274
|
+
import traceback
|
|
275
|
+
ASCIIColors.red(f"Server: Traceback:\n{traceback.format_exc()}")
|