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.

@@ -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
- config: Dict[str, Any] = None) -> Optional[LollmsTTSBinding]:
56
- if config is None:
57
- config = {}
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
- try:
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.start_server()
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
- print("XTTS Client: Starting dedicated server...")
33
- binding_root = Path(__file__).parent
34
- server_dir = binding_root / "server"
35
- requirements_file = server_dir / "requirements.txt"
36
- server_script = server_dir / "main.py"
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
- pm_v = pm.PackageManager(venv_path=venv_path)
41
- pm_v.ensure_requirements(str(requirements_file))
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 with stdout/stderr forwarded to console
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
- # Forward stdout and stderr to the parent process console
58
- self.server_process = subprocess.Popen(
59
- command,
60
- stdout=None, # Inherit parent's stdout (shows in console)
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
- try:
71
- response = requests.get(f"{self.base_url}/status")
72
- if response.status_code == 200 and response.json().get("status") == "running":
73
- print("XTTS Server is up and running.")
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
- self.stop_server()
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: Stopping dedicated server...")
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
- # Ensure the server is stopped when the object is destroyed
91
- self.stop_server()
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", [])