lollms-client 1.6.0__py3-none-any.whl → 1.6.2__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 +286 -281
- lollms_client/lollms_tts_binding.py +15 -13
- lollms_client/tts_bindings/xtts/__init__.py +90 -37
- lollms_client/tts_bindings/xtts/server/main.py +282 -279
- {lollms_client-1.6.0.dist-info → lollms_client-1.6.2.dist-info}/METADATA +5 -2
- {lollms_client-1.6.0.dist-info → lollms_client-1.6.2.dist-info}/RECORD +10 -10
- {lollms_client-1.6.0.dist-info → lollms_client-1.6.2.dist-info}/WHEEL +0 -0
- {lollms_client-1.6.0.dist-info → lollms_client-1.6.2.dist-info}/licenses/LICENSE +0 -0
- {lollms_client-1.6.0.dist-info → lollms_client-1.6.2.dist-info}/top_level.txt +0 -0
|
@@ -49,26 +49,28 @@ class LollmsTTSBindingManager:
|
|
|
49
49
|
except Exception as e:
|
|
50
50
|
trace_exception(e)
|
|
51
51
|
print(f"Failed to load TTS binding {binding_name}: {str(e)}")
|
|
52
|
-
|
|
53
|
-
def create_binding(self,
|
|
52
|
+
def create_binding(self,
|
|
54
53
|
binding_name: str,
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
**kwargs) -> Optional[LollmsTTSBinding]:
|
|
55
|
+
"""
|
|
56
|
+
Create an instance of a specific binding.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
binding_name (str): Name of the binding to create.
|
|
60
|
+
kwargs: binding specific arguments
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Optional[LollmsLLMBinding]: Binding instance or None if creation failed.
|
|
64
|
+
"""
|
|
59
65
|
if binding_name not in self.available_bindings:
|
|
60
66
|
self._load_binding(binding_name)
|
|
61
|
-
|
|
67
|
+
|
|
62
68
|
binding_class = self.available_bindings.get(binding_name)
|
|
63
69
|
if binding_class:
|
|
64
|
-
|
|
65
|
-
return binding_class(**config)
|
|
66
|
-
except Exception as e:
|
|
67
|
-
trace_exception(e)
|
|
68
|
-
print(f"Failed to instantiate TTS binding {binding_name}: {str(e)}")
|
|
69
|
-
return None
|
|
70
|
+
return binding_class(**kwargs)
|
|
70
71
|
return None
|
|
71
72
|
|
|
73
|
+
|
|
72
74
|
@staticmethod
|
|
73
75
|
def _get_fallback_description(binding_name: str) -> Dict:
|
|
74
76
|
return {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# File: lollms_client/tts_bindings/xtts/__init__.py
|
|
2
1
|
from lollms_client.lollms_tts_binding import LollmsTTSBinding
|
|
3
2
|
from typing import Optional, List
|
|
4
3
|
from pathlib import Path
|
|
@@ -8,6 +7,14 @@ import sys
|
|
|
8
7
|
import time
|
|
9
8
|
import pipmaster as pm
|
|
10
9
|
|
|
10
|
+
# New import for process-safe file locking
|
|
11
|
+
try:
|
|
12
|
+
from filelock import FileLock, Timeout
|
|
13
|
+
except ImportError:
|
|
14
|
+
print("FATAL: The 'filelock' library is required. Please install it by running: pip install filelock")
|
|
15
|
+
sys.exit(1)
|
|
16
|
+
|
|
17
|
+
|
|
11
18
|
BindingName = "XTTSClientBinding"
|
|
12
19
|
|
|
13
20
|
class XTTSClientBinding(LollmsTTSBinding):
|
|
@@ -24,21 +31,73 @@ class XTTSClientBinding(LollmsTTSBinding):
|
|
|
24
31
|
self.auto_start_server = auto_start_server
|
|
25
32
|
self.server_process = None
|
|
26
33
|
self.base_url = f"http://{self.host}:{self.port}"
|
|
34
|
+
self.binding_root = Path(__file__).parent
|
|
35
|
+
self.server_dir = self.binding_root / "server"
|
|
27
36
|
|
|
28
37
|
if self.auto_start_server:
|
|
29
|
-
self.
|
|
38
|
+
self.ensure_server_is_running()
|
|
39
|
+
|
|
40
|
+
def is_server_running(self) -> bool:
|
|
41
|
+
"""Checks if the server is already running and responsive."""
|
|
42
|
+
try:
|
|
43
|
+
response = requests.get(f"{self.base_url}/status", timeout=1)
|
|
44
|
+
if response.status_code == 200 and response.json().get("status") == "running":
|
|
45
|
+
return True
|
|
46
|
+
except requests.ConnectionError:
|
|
47
|
+
return False
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
def ensure_server_is_running(self):
|
|
51
|
+
"""
|
|
52
|
+
Ensures the XTTS server is running. If not, it attempts to start it
|
|
53
|
+
in a process-safe manner using a file lock.
|
|
54
|
+
"""
|
|
55
|
+
if self.is_server_running():
|
|
56
|
+
print("XTTS Server is already running.")
|
|
57
|
+
return
|
|
58
|
+
|
|
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
|
+
try:
|
|
64
|
+
with lock:
|
|
65
|
+
# Double-check after acquiring the lock to handle race conditions
|
|
66
|
+
if not self.is_server_running():
|
|
67
|
+
print("Lock acquired. Starting dedicated XTTS server...")
|
|
68
|
+
self.start_server()
|
|
69
|
+
else:
|
|
70
|
+
print("Server was started by another process while waiting for the lock.")
|
|
71
|
+
except Timeout:
|
|
72
|
+
print("Could not acquire lock. Another process is likely starting the server. Waiting...")
|
|
73
|
+
|
|
74
|
+
# All workers (the one that started the server and those that waited) will verify the server is ready
|
|
75
|
+
self._wait_for_server()
|
|
76
|
+
|
|
30
77
|
|
|
31
78
|
def start_server(self):
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
79
|
+
"""
|
|
80
|
+
Installs dependencies and launches the server as a background subprocess.
|
|
81
|
+
This method should only be called from within a file lock.
|
|
82
|
+
"""
|
|
83
|
+
requirements_file = self.server_dir / "requirements.txt"
|
|
84
|
+
server_script = self.server_dir / "main.py"
|
|
37
85
|
|
|
38
86
|
# 1. Ensure a virtual environment and dependencies
|
|
39
|
-
venv_path = server_dir / "venv"
|
|
40
|
-
|
|
41
|
-
pm_v.
|
|
87
|
+
venv_path = self.server_dir / "venv"
|
|
88
|
+
print(f"Ensuring virtual environment and dependencies in: {venv_path}")
|
|
89
|
+
pm_v = pm.PackageManager(venv_path=str(venv_path))
|
|
90
|
+
|
|
91
|
+
success = pm_v.ensure_requirements(
|
|
92
|
+
str(requirements_file),
|
|
93
|
+
verbose=True
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if not success:
|
|
97
|
+
print("FATAL: Failed to install server dependencies. Aborting launch.")
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
print("Dependencies are satisfied. Proceeding to launch server...")
|
|
42
101
|
|
|
43
102
|
# 2. Get the python executable from the venv
|
|
44
103
|
if sys.platform == "win32":
|
|
@@ -46,7 +105,7 @@ class XTTSClientBinding(LollmsTTSBinding):
|
|
|
46
105
|
else:
|
|
47
106
|
python_executable = venv_path / "bin" / "python"
|
|
48
107
|
|
|
49
|
-
# 3. Launch the server as a subprocess
|
|
108
|
+
# 3. Launch the server as a detached subprocess
|
|
50
109
|
command = [
|
|
51
110
|
str(python_executable),
|
|
52
111
|
str(server_script),
|
|
@@ -54,41 +113,36 @@ class XTTSClientBinding(LollmsTTSBinding):
|
|
|
54
113
|
"--port", str(self.port)
|
|
55
114
|
]
|
|
56
115
|
|
|
57
|
-
#
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
stderr=None, # Inherit parent's stderr (shows in console)
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
# 4. Wait for the server to be ready
|
|
65
|
-
self._wait_for_server()
|
|
116
|
+
# The server is started as a background process and is not tied to this specific worker's lifecycle
|
|
117
|
+
subprocess.Popen(command)
|
|
118
|
+
print("XTTS Server process launched in the background.")
|
|
119
|
+
|
|
66
120
|
|
|
67
121
|
def _wait_for_server(self, timeout=60):
|
|
122
|
+
print("Waiting for XTTS server to become available...")
|
|
68
123
|
start_time = time.time()
|
|
69
124
|
while time.time() - start_time < timeout:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return
|
|
75
|
-
except requests.ConnectionError:
|
|
76
|
-
time.sleep(1)
|
|
125
|
+
if self.is_server_running():
|
|
126
|
+
print("XTTS Server is up and running.")
|
|
127
|
+
return
|
|
128
|
+
time.sleep(1)
|
|
77
129
|
|
|
78
|
-
|
|
79
|
-
raise RuntimeError("Failed to start the XTTS server in the specified timeout.")
|
|
130
|
+
raise RuntimeError("Failed to connect to the XTTS server within the specified timeout.")
|
|
80
131
|
|
|
81
132
|
def stop_server(self):
|
|
133
|
+
"""
|
|
134
|
+
In a multi-worker setup, a single client instance should not stop the shared server.
|
|
135
|
+
The server will continue running until the main application is terminated.
|
|
136
|
+
"""
|
|
82
137
|
if self.server_process:
|
|
83
|
-
print("XTTS Client:
|
|
84
|
-
self.server_process.terminate()
|
|
85
|
-
self.server_process.wait()
|
|
138
|
+
print("XTTS Client: An instance is shutting down, but the shared server will remain active for other workers.")
|
|
86
139
|
self.server_process = None
|
|
87
|
-
print("Server stopped.")
|
|
88
140
|
|
|
89
141
|
def __del__(self):
|
|
90
|
-
|
|
91
|
-
|
|
142
|
+
"""
|
|
143
|
+
The destructor does not stop the server to prevent disrupting other workers.
|
|
144
|
+
"""
|
|
145
|
+
pass
|
|
92
146
|
|
|
93
147
|
def generate_audio(self, text: str, voice: Optional[str] = None, **kwargs) -> bytes:
|
|
94
148
|
"""Generate audio by calling the server's API"""
|
|
@@ -107,5 +161,4 @@ class XTTSClientBinding(LollmsTTSBinding):
|
|
|
107
161
|
"""Get available models from the server"""
|
|
108
162
|
response = requests.get(f"{self.base_url}/list_models")
|
|
109
163
|
response.raise_for_status()
|
|
110
|
-
return response.json().get("models", [])
|
|
111
|
-
|
|
164
|
+
return response.json().get("models", [])
|