lollms-client 1.6.4__py3-none-any.whl → 1.6.6__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.
Potentially problematic release.
This version of lollms-client might be problematic. Click here for more details.
- lollms_client/__init__.py +1 -1
- lollms_client/lollms_core.py +3 -1
- lollms_client/tti_bindings/diffusers/__init__.py +129 -59
- lollms_client/tti_bindings/diffusers/server/main.py +354 -118
- lollms_client/tti_bindings/gemini/__init__.py +179 -239
- lollms_client/tts_bindings/xtts/__init__.py +106 -81
- lollms_client/tts_bindings/xtts/server/main.py +128 -183
- {lollms_client-1.6.4.dist-info → lollms_client-1.6.6.dist-info}/METADATA +1 -1
- {lollms_client-1.6.4.dist-info → lollms_client-1.6.6.dist-info}/RECORD +12 -12
- {lollms_client-1.6.4.dist-info → lollms_client-1.6.6.dist-info}/WHEEL +0 -0
- {lollms_client-1.6.4.dist-info → lollms_client-1.6.6.dist-info}/licenses/LICENSE +0 -0
- {lollms_client-1.6.4.dist-info → lollms_client-1.6.6.dist-info}/top_level.txt +0 -0
|
@@ -1,38 +1,51 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
from pathlib import Path
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
4
3
|
import requests
|
|
5
4
|
import subprocess
|
|
6
|
-
import sys
|
|
7
5
|
import time
|
|
8
|
-
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional, List
|
|
9
8
|
|
|
10
|
-
#
|
|
9
|
+
# Ensure pipmaster is available.
|
|
10
|
+
try:
|
|
11
|
+
import pipmaster as pm
|
|
12
|
+
except ImportError:
|
|
13
|
+
print("FATAL: pipmaster is not installed. Please install it using: pip install pipmaster")
|
|
14
|
+
sys.exit(1)
|
|
15
|
+
|
|
16
|
+
# Ensure filelock is available for process-safe server startup.
|
|
11
17
|
try:
|
|
12
18
|
from filelock import FileLock, Timeout
|
|
13
19
|
except ImportError:
|
|
14
20
|
print("FATAL: The 'filelock' library is required. Please install it by running: pip install filelock")
|
|
15
21
|
sys.exit(1)
|
|
16
22
|
|
|
23
|
+
from lollms_client.lollms_tts_binding import LollmsTTSBinding
|
|
24
|
+
from ascii_colors import ASCIIColors
|
|
17
25
|
|
|
18
26
|
BindingName = "XTTSClientBinding"
|
|
19
27
|
|
|
20
28
|
class XTTSClientBinding(LollmsTTSBinding):
|
|
29
|
+
"""
|
|
30
|
+
Client binding for a dedicated, managed XTTS server.
|
|
31
|
+
This architecture prevents the heavy XTTS model from being loaded into memory
|
|
32
|
+
by multiple worker processes, solving potential OOM errors and speeding up TTS generation.
|
|
33
|
+
"""
|
|
21
34
|
def __init__(self,
|
|
22
|
-
host: str = "localhost",
|
|
23
|
-
port: int = 8081,
|
|
24
|
-
auto_start_server: bool = True,
|
|
25
35
|
**kwargs):
|
|
26
36
|
|
|
27
37
|
binding_name = "xtts"
|
|
28
38
|
super().__init__(binding_name=binding_name, **kwargs)
|
|
29
|
-
|
|
30
|
-
self.
|
|
31
|
-
self.
|
|
39
|
+
|
|
40
|
+
self.config = kwargs
|
|
41
|
+
self.host = kwargs.get("host", "localhost")
|
|
42
|
+
self.port = kwargs.get("port", 8081)
|
|
43
|
+
self.auto_start_server = kwargs.get("auto_start_server", True)
|
|
32
44
|
self.server_process = None
|
|
33
45
|
self.base_url = f"http://{self.host}:{self.port}"
|
|
34
46
|
self.binding_root = Path(__file__).parent
|
|
35
47
|
self.server_dir = self.binding_root / "server"
|
|
48
|
+
self.venv_dir = Path("./venv/tts_xtts_venv")
|
|
36
49
|
|
|
37
50
|
if self.auto_start_server:
|
|
38
51
|
self.ensure_server_is_running()
|
|
@@ -40,10 +53,10 @@ class XTTSClientBinding(LollmsTTSBinding):
|
|
|
40
53
|
def is_server_running(self) -> bool:
|
|
41
54
|
"""Checks if the server is already running and responsive."""
|
|
42
55
|
try:
|
|
43
|
-
response = requests.get(f"{self.base_url}/status", timeout=
|
|
56
|
+
response = requests.get(f"{self.base_url}/status", timeout=2)
|
|
44
57
|
if response.status_code == 200 and response.json().get("status") == "running":
|
|
45
58
|
return True
|
|
46
|
-
except requests.
|
|
59
|
+
except requests.exceptions.RequestException:
|
|
47
60
|
return False
|
|
48
61
|
return False
|
|
49
62
|
|
|
@@ -52,64 +65,69 @@ class XTTSClientBinding(LollmsTTSBinding):
|
|
|
52
65
|
Ensures the XTTS server is running. If not, it attempts to start it
|
|
53
66
|
in a process-safe manner using a file lock.
|
|
54
67
|
"""
|
|
68
|
+
self.server_dir.mkdir(exist_ok=True)
|
|
69
|
+
lock_path = self.server_dir / "xtts_server.lock"
|
|
70
|
+
lock = FileLock(lock_path)
|
|
71
|
+
|
|
72
|
+
ASCIIColors.info("Attempting to start or connect to the XTTS server...")
|
|
73
|
+
|
|
55
74
|
if self.is_server_running():
|
|
56
|
-
|
|
75
|
+
ASCIIColors.green("XTTS Server is already running and responsive.")
|
|
57
76
|
return
|
|
58
77
|
|
|
59
|
-
lock_path = self.server_dir / "xtts_server.lock"
|
|
60
|
-
lock = FileLock(lock_path, timeout=10) # Wait a maximum of 10 seconds for the lock
|
|
61
|
-
|
|
62
|
-
print("Attempting to start or wait for the XTTS server...")
|
|
63
78
|
try:
|
|
64
|
-
with lock:
|
|
65
|
-
# Double-check after acquiring the lock to handle race conditions
|
|
79
|
+
with lock.acquire(timeout=60):
|
|
66
80
|
if not self.is_server_running():
|
|
67
|
-
|
|
81
|
+
ASCIIColors.yellow("Lock acquired. Starting dedicated XTTS server...")
|
|
68
82
|
self.start_server()
|
|
83
|
+
self._wait_for_server()
|
|
69
84
|
else:
|
|
70
|
-
|
|
85
|
+
ASCIIColors.green("Server was started by another process while we waited. Connected successfully.")
|
|
71
86
|
except Timeout:
|
|
72
|
-
|
|
87
|
+
ASCIIColors.yellow("Could not acquire lock, another process is starting the server. Waiting...")
|
|
88
|
+
self._wait_for_server(timeout=180)
|
|
73
89
|
|
|
74
|
-
|
|
75
|
-
|
|
90
|
+
if not self.is_server_running():
|
|
91
|
+
raise RuntimeError("Failed to start or connect to the XTTS server after all attempts.")
|
|
76
92
|
|
|
77
|
-
def install(self, venv_path, requirements_file):
|
|
78
|
-
print(f"Ensuring virtual environment and dependencies in: {venv_path}")
|
|
79
|
-
pm_v = pm.PackageManager(venv_path=str(venv_path))
|
|
80
93
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
94
|
+
def install_server_dependencies(self):
|
|
95
|
+
"""
|
|
96
|
+
Installs the server's dependencies into a dedicated virtual environment
|
|
97
|
+
using pipmaster, which handles complex packages like PyTorch.
|
|
98
|
+
"""
|
|
99
|
+
ASCIIColors.info(f"Setting up virtual environment in: {self.venv_dir}")
|
|
100
|
+
pm_v = pm.PackageManager(venv_path=str(self.venv_dir))
|
|
101
|
+
|
|
102
|
+
requirements_file = self.server_dir / "requirements.txt"
|
|
103
|
+
|
|
104
|
+
ASCIIColors.info("Installing server dependencies from requirements.txt...")
|
|
105
|
+
success = pm_v.ensure_requirements(str(requirements_file), verbose=True)
|
|
85
106
|
|
|
86
107
|
if not success:
|
|
87
|
-
|
|
88
|
-
|
|
108
|
+
ASCIIColors.error("Failed to install server dependencies. Please check the console output for errors.")
|
|
109
|
+
raise RuntimeError("XTTS server dependency installation failed.")
|
|
110
|
+
|
|
111
|
+
ASCIIColors.green("Server dependencies are satisfied.")
|
|
89
112
|
|
|
90
|
-
print("Dependencies are satisfied. Proceeding to launch server...")
|
|
91
113
|
|
|
92
114
|
def start_server(self):
|
|
93
115
|
"""
|
|
94
|
-
Installs dependencies and launches the server as a background subprocess.
|
|
116
|
+
Installs dependencies and launches the FastAPI server as a background subprocess.
|
|
95
117
|
This method should only be called from within a file lock.
|
|
96
118
|
"""
|
|
97
|
-
requirements_file = self.server_dir / "requirements.txt"
|
|
98
119
|
server_script = self.server_dir / "main.py"
|
|
120
|
+
if not server_script.exists():
|
|
121
|
+
raise FileNotFoundError(f"Server script not found at {server_script}.")
|
|
99
122
|
|
|
100
|
-
|
|
101
|
-
|
|
123
|
+
if not self.venv_dir.exists():
|
|
124
|
+
self.install_server_dependencies()
|
|
102
125
|
|
|
103
|
-
if not venv_path.exists():
|
|
104
|
-
self.install(venv_path, requirements_file)
|
|
105
|
-
|
|
106
|
-
# 2. Get the python executable from the venv
|
|
107
126
|
if sys.platform == "win32":
|
|
108
|
-
python_executable =
|
|
127
|
+
python_executable = self.venv_dir / "Scripts" / "python.exe"
|
|
109
128
|
else:
|
|
110
|
-
python_executable =
|
|
129
|
+
python_executable = self.venv_dir / "bin" / "python"
|
|
111
130
|
|
|
112
|
-
# 3. Launch the server as a detached subprocess
|
|
113
131
|
command = [
|
|
114
132
|
str(python_executable),
|
|
115
133
|
str(server_script),
|
|
@@ -117,54 +135,61 @@ class XTTSClientBinding(LollmsTTSBinding):
|
|
|
117
135
|
"--port", str(self.port)
|
|
118
136
|
]
|
|
119
137
|
|
|
120
|
-
#
|
|
121
|
-
subprocess.
|
|
122
|
-
|
|
123
|
-
|
|
138
|
+
# Use DETACHED_PROCESS on Windows to allow the server to run independently.
|
|
139
|
+
creationflags = subprocess.DETACHED_PROCESS if sys.platform == "win32" else 0
|
|
140
|
+
|
|
141
|
+
self.server_process = subprocess.Popen(command, creationflags=creationflags)
|
|
142
|
+
ASCIIColors.info("XTTS server process launched in the background.")
|
|
124
143
|
|
|
125
|
-
def _wait_for_server(self, timeout=
|
|
126
|
-
|
|
144
|
+
def _wait_for_server(self, timeout=120):
|
|
145
|
+
"""Waits for the server to become responsive."""
|
|
146
|
+
ASCIIColors.info("Waiting for XTTS server to become available...")
|
|
127
147
|
start_time = time.time()
|
|
128
148
|
while time.time() - start_time < timeout:
|
|
129
149
|
if self.is_server_running():
|
|
130
|
-
|
|
150
|
+
ASCIIColors.green("XTTS Server is up and running.")
|
|
131
151
|
return
|
|
132
|
-
time.sleep(
|
|
133
|
-
|
|
152
|
+
time.sleep(2)
|
|
134
153
|
raise RuntimeError("Failed to connect to the XTTS server within the specified timeout.")
|
|
135
154
|
|
|
136
|
-
def stop_server(self):
|
|
137
|
-
"""
|
|
138
|
-
In a multi-worker setup, a single client instance should not stop the shared server.
|
|
139
|
-
The server will continue running until the main application is terminated.
|
|
140
|
-
"""
|
|
141
|
-
if self.server_process:
|
|
142
|
-
print("XTTS Client: An instance is shutting down, but the shared server will remain active for other workers.")
|
|
143
|
-
self.server_process = None
|
|
144
|
-
|
|
145
155
|
def __del__(self):
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
"""
|
|
156
|
+
# The client destructor does not stop the server,
|
|
157
|
+
# as it is a shared resource for other processes.
|
|
149
158
|
pass
|
|
150
159
|
|
|
151
160
|
def generate_audio(self, text: str, voice: Optional[str] = None, **kwargs) -> bytes:
|
|
152
161
|
"""Generate audio by calling the server's API"""
|
|
153
|
-
payload = {"text": text, "voice": voice
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
162
|
+
payload = {"text": text, "voice": voice}
|
|
163
|
+
# Pass other kwargs from the description file (language, split_sentences)
|
|
164
|
+
payload.update(kwargs)
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
response = requests.post(f"{self.base_url}/generate_audio", json=payload, timeout=300)
|
|
168
|
+
response.raise_for_status()
|
|
169
|
+
return response.content
|
|
170
|
+
except requests.exceptions.RequestException as e:
|
|
171
|
+
ASCIIColors.error(f"Failed to communicate with XTTS server at {self.base_url}.")
|
|
172
|
+
ASCIIColors.error(f"Error details: {e}")
|
|
173
|
+
raise RuntimeError("Communication with the XTTS server failed.") from e
|
|
174
|
+
|
|
157
175
|
|
|
158
176
|
def list_voices(self, **kwargs) -> List[str]:
|
|
159
177
|
"""Get available voices from the server"""
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
178
|
+
try:
|
|
179
|
+
response = requests.get(f"{self.base_url}/list_voices")
|
|
180
|
+
response.raise_for_status()
|
|
181
|
+
return response.json().get("voices", [])
|
|
182
|
+
except requests.exceptions.RequestException as e:
|
|
183
|
+
ASCIIColors.error(f"Failed to get voices from XTTS server: {e}")
|
|
184
|
+
return []
|
|
163
185
|
|
|
164
186
|
|
|
165
|
-
def list_models(self) -> list:
|
|
166
|
-
"""Lists models"""
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
187
|
+
def list_models(self, **kwargs) -> list:
|
|
188
|
+
"""Lists models supported by the server"""
|
|
189
|
+
try:
|
|
190
|
+
response = requests.get(f"{self.base_url}/list_models")
|
|
191
|
+
response.raise_for_status()
|
|
192
|
+
return response.json().get("models", [])
|
|
193
|
+
except requests.exceptions.RequestException as e:
|
|
194
|
+
ASCIIColors.error(f"Failed to get models from XTTS server: {e}")
|
|
195
|
+
return []
|