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.

@@ -1,38 +1,51 @@
1
- from lollms_client.lollms_tts_binding import LollmsTTSBinding
2
- from typing import Optional, List
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
- import pipmaster as pm
6
+ from pathlib import Path
7
+ from typing import Optional, List
9
8
 
10
- # New import for process-safe file locking
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
- self.host = host
30
- self.port = port
31
- self.auto_start_server = auto_start_server
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=1)
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.ConnectionError:
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
- print("XTTS Server is already running.")
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
- print("Lock acquired. Starting dedicated XTTS server...")
81
+ ASCIIColors.yellow("Lock acquired. Starting dedicated XTTS server...")
68
82
  self.start_server()
83
+ self._wait_for_server()
69
84
  else:
70
- print("Server was started by another process while waiting for the lock.")
85
+ ASCIIColors.green("Server was started by another process while we waited. Connected successfully.")
71
86
  except Timeout:
72
- print("Could not acquire lock. Another process is likely starting the server. Waiting...")
87
+ ASCIIColors.yellow("Could not acquire lock, another process is starting the server. Waiting...")
88
+ self._wait_for_server(timeout=180)
73
89
 
74
- # All workers (the one that started the server and those that waited) will verify the server is ready
75
- self._wait_for_server()
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
- success = pm_v.ensure_requirements(
82
- str(requirements_file),
83
- verbose=True
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
- print("FATAL: Failed to install server dependencies. Aborting launch.")
88
- return
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
- # 1. Ensure a virtual environment and dependencies
101
- venv_path = Path("./venv/xtts_venv")
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 = venv_path / "Scripts" / "python.exe"
127
+ python_executable = self.venv_dir / "Scripts" / "python.exe"
109
128
  else:
110
- python_executable = venv_path / "bin" / "python"
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
- # The server is started as a background process and is not tied to this specific worker's lifecycle
121
- subprocess.Popen(command)
122
- print("XTTS Server process launched in the background.")
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=60):
126
- print("Waiting for XTTS server to become available...")
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
- print("XTTS Server is up and running.")
150
+ ASCIIColors.green("XTTS Server is up and running.")
131
151
  return
132
- time.sleep(1)
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
- The destructor does not stop the server to prevent disrupting other workers.
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, **kwargs}
154
- response = requests.post(f"{self.base_url}/generate_audio", json=payload)
155
- response.raise_for_status()
156
- return response.content
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
- response = requests.get(f"{self.base_url}/list_voices")
161
- response.raise_for_status()
162
- return response.json().get("voices", [])
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
- response = requests.get(f"{self.base_url}/list_models")
168
- response.raise_for_status()
169
- return response.json().get("models", [])
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 []