lollms-client 1.6.1__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 CHANGED
@@ -8,7 +8,7 @@ from lollms_client.lollms_utilities import PromptReshaper # Keep general utiliti
8
8
  from lollms_client.lollms_mcp_binding import LollmsMCPBinding, LollmsMCPBindingManager
9
9
  from lollms_client.lollms_llm_binding import LollmsLLMBindingManager
10
10
 
11
- __version__ = "1.6.1" # Updated version
11
+ __version__ = "1.6.2" # Updated version
12
12
 
13
13
  # Optionally, you could define __all__ if you want to be explicit about exports
14
14
  __all__ = [
@@ -143,16 +143,21 @@ class LollmsClient():
143
143
  ASCIIColors.warning(f"Failed to create LLM binding: {llm_binding_name}. Available: {available}")
144
144
 
145
145
  if tts_binding_name:
146
- self.tts = self.tts_binding_manager.create_binding(
147
- binding_name=tts_binding_name,
148
- **{
149
- k: v
150
- for k, v in (tts_binding_config or {}).items()
151
- if k != "binding_name"
152
- }
153
- )
154
- if self.tts is None:
155
- ASCIIColors.warning(f"Failed to create TTS binding: {tts_binding_name}. Available: {self.tts_binding_manager.get_available_bindings()}")
146
+ try:
147
+ params = {
148
+ k: v
149
+ for k, v in (tts_binding_config or {}).items()
150
+ if k != "binding_name"
151
+ }
152
+ self.tts = self.tts_binding_manager.create_binding(
153
+ binding_name=tts_binding_name,
154
+ **params
155
+ )
156
+ if self.tts is None:
157
+ ASCIIColors.warning(f"Failed to create TTS binding: {tts_binding_name}. Available: {self.tts_binding_manager.get_available_bindings()}")
158
+ except Exception as e:
159
+ trace_exception(e)
160
+ ASCIIColors.warning(f"Exception occurred while creating TTS binding: {str(e)}")
156
161
 
157
162
  if tti_binding_name:
158
163
  if tti_binding_config:
@@ -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", [])
@@ -1,314 +1,317 @@
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
- print("Server: Loading XTTS dependencies...")
19
- import torch
20
- import torchaudio
21
- from TTS.api import TTS
22
- print("Server: XTTS dependencies loaded successfully")
23
-
24
- # Check for CUDA availability
25
- device = "cuda" if torch.cuda.is_available() else "cpu"
26
- print(f"Server: Using device: {device}")
27
-
28
- xtts_available = True
29
-
30
- except Exception as e:
31
- print(f"Server: Failed to load XTTS dependencies: {e}")
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
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
41
16
 
42
- class XTTSServer:
43
- def __init__(self):
44
- self.model = None
45
- self.model_loaded = False
46
- self.model_loading = False # Flag to prevent concurrent loading
47
- self.available_voices = self._load_available_voices()
48
- self.available_models = ["xtts_v2"]
17
+ # --- XTTS Implementation ---
18
+ try:
19
+ print("Server: Loading XTTS dependencies...")
20
+ import torch
21
+ import torchaudio
22
+ from TTS.api import TTS
23
+ print("Server: XTTS dependencies loaded successfully")
49
24
 
50
- # Don't initialize model here - do it lazily on first request
51
- print("Server: XTTS server initialized (model will be loaded on first request)")
52
-
53
- async def _ensure_model_loaded(self):
54
- """Ensure the XTTS model is loaded (lazy loading)"""
55
- if self.model_loaded:
56
- return
57
-
58
- if self.model_loading:
59
- # Another request is already loading the model, wait for it
60
- while self.model_loading and not self.model_loaded:
61
- await asyncio.sleep(0.1)
62
- return
63
-
64
- if not xtts_available:
65
- raise RuntimeError("XTTS library not available")
66
-
67
- try:
68
- self.model_loading = True
69
- print("Server: Loading XTTS model for the first time (this may take a few minutes)...")
70
-
71
- # Initialize XTTS model
72
- self.model = TTS("tts_models/multilingual/multi-dataset/xtts_v2").to(device)
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()}")
25
+ # Check for CUDA availability
26
+ device = "cuda" if torch.cuda.is_available() else "cpu"
27
+ print(f"Server: Using device: {device}")
28
+
29
+ xtts_available = True
30
+
31
+ except Exception as e:
32
+ print(f"Server: Failed to load XTTS dependencies: {e}")
33
+ print(f"Server: Traceback:\n{traceback.format_exc()}")
34
+ xtts_available = False
35
+
36
+ # --- API Models ---
37
+ class GenerationRequest(BaseModel):
38
+ text: str
39
+ voice: Optional[str] = None
40
+ language: Optional[str] = "en"
41
+ speaker_wav: Optional[str] = None
42
+
43
+ class XTTSServer:
44
+ def __init__(self):
45
+ self.model = None
80
46
  self.model_loaded = False
81
- raise
82
- finally:
83
- self.model_loading = False
84
-
85
- def _load_available_voices(self) -> List[str]:
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 = []
47
+ self.model_loading = False # Flag to prevent concurrent loading
48
+ self.available_voices = self._load_available_voices()
49
+ self.available_models = ["xtts_v2"]
91
50
 
92
- if voices_dir.exists():
93
- # Look for WAV files in voices directory
94
- for voice_file in voices_dir.glob("*.wav"):
95
- voices.append(voice_file.stem)
96
-
97
- # If no custom voices found, provide some default names
98
- if not voices:
99
- voices = ["default", "female", "male"]
51
+ # Don't initialize model here - do it lazily on first request
52
+ print("Server: XTTS server initialized (model will be loaded on first request)")
53
+
54
+ async def _ensure_model_loaded(self):
55
+ """Ensure the XTTS model is loaded (lazy loading)"""
56
+ if self.model_loaded:
57
+ return
100
58
 
101
- return voices
102
-
103
- except Exception as e:
104
- print(f"Server: Error loading voices: {e}")
105
- return ["default"]
106
-
107
- async def generate_audio(self, text: str, voice: Optional[str] = None,
108
- language: str = "en", speaker_wav: Optional[str] = None) -> bytes:
109
- """Generate audio from text using XTTS"""
110
- # Ensure model is loaded before proceeding
111
- await self._ensure_model_loaded()
59
+ if self.model_loading:
60
+ # Another request is already loading the model, wait for it
61
+ while self.model_loading and not self.model_loaded:
62
+ await asyncio.sleep(0.1)
63
+ return
64
+
65
+ if not xtts_available:
66
+ raise RuntimeError("XTTS library not available")
67
+
68
+ try:
69
+ self.model_loading = True
70
+ print("Server: Loading XTTS model for the first time (this may take a few minutes)...")
71
+
72
+ # Initialize XTTS model
73
+ self.model = TTS("tts_models/multilingual/multi-dataset/xtts_v2").to(device)
74
+
75
+ self.model_loaded = True
76
+ print("Server: XTTS model loaded successfully")
77
+
78
+ except Exception as e:
79
+ print(f"Server: Error loading XTTS model: {e}")
80
+ print(f"Server: Traceback:\n{traceback.format_exc()}")
81
+ self.model_loaded = False
82
+ raise
83
+ finally:
84
+ self.model_loading = False
112
85
 
113
- if not self.model_loaded or self.model is None:
114
- raise RuntimeError("XTTS model failed to load")
86
+ def _load_available_voices(self) -> List[str]:
87
+ """Load and return available voices"""
88
+ try:
89
+ # Look for voice files in voices directory
90
+ voices_dir = Path(__file__).parent / "voices"
91
+ voices = []
92
+
93
+ if voices_dir.exists():
94
+ # Look for WAV files in voices directory
95
+ for voice_file in voices_dir.glob("*.wav"):
96
+ voices.append(voice_file.stem)
97
+
98
+ # If no custom voices found, provide some default names
99
+ if not voices:
100
+ voices = ["default", "female", "male"]
101
+
102
+ return voices
103
+
104
+ except Exception as e:
105
+ print(f"Server: Error loading voices: {e}")
106
+ return ["default"]
115
107
 
116
- try:
117
- print(f"Server: Generating audio for: '{text[:50]}{'...' if len(text) > 50 else ''}'")
118
- print(f"Server: Using voice: {voice}, language: {language}")
119
-
120
- # Handle voice/speaker selection
121
- speaker_wav_path = None
122
-
123
- # First priority: use provided speaker_wav parameter
124
- if speaker_wav:
125
- speaker_wav_path = speaker_wav
126
- print(f"Server: Using provided speaker_wav: {speaker_wav_path}")
108
+ async def generate_audio(self, text: str, voice: Optional[str] = None,
109
+ language: str = "en", speaker_wav: Optional[str] = None) -> bytes:
110
+ """Generate audio from text using XTTS"""
111
+ # Ensure model is loaded before proceeding
112
+ await self._ensure_model_loaded()
127
113
 
128
- # Second priority: check if voice parameter is a file path
129
- elif voice and voice != "default":
130
- if os.path.exists(voice):
131
- # Voice parameter is a full file path
132
- speaker_wav_path = voice
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")
143
-
144
- # Create a temporary file for output
145
- with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as temp_file:
146
- temp_output_path = temp_file.name
114
+ if not self.model_loaded or self.model is None:
115
+ raise RuntimeError("XTTS model failed to load")
147
116
 
148
117
  try:
149
- # Generate audio using XTTS
150
- if speaker_wav_path and os.path.exists(speaker_wav_path):
151
- print(f"Server: Generating with speaker reference: {speaker_wav_path}")
152
- self.model.tts_to_file(
153
- text=text,
154
- speaker_wav=speaker_wav_path,
155
- language=language,
156
- file_path=temp_output_path
157
- )
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}")
118
+ print(f"Server: Generating audio for: '{text[:50]}{'...' if len(text) > 50 else ''}'")
119
+ print(f"Server: Using voice: {voice}, language: {language}")
120
+
121
+ # Handle voice/speaker selection
122
+ speaker_wav_path = None
123
+
124
+ # First priority: use provided speaker_wav parameter
125
+ if speaker_wav:
126
+ speaker_wav_path = speaker_wav
127
+ print(f"Server: Using provided speaker_wav: {speaker_wav_path}")
128
+
129
+ # Second priority: check if voice parameter is a file path
130
+ elif voice and voice != "default":
131
+ if os.path.exists(voice):
132
+ # Voice parameter is a full file path
133
+ speaker_wav_path = voice
134
+ print(f"Server: Using voice as file path: {speaker_wav_path}")
135
+ else:
136
+ # Look for voice file in voices directory
137
+ voices_dir = Path(__file__).parent / "voices"
138
+ potential_voice_path = voices_dir / f"{voice}.wav"
139
+ if potential_voice_path.exists():
140
+ speaker_wav_path = str(potential_voice_path)
141
+ print(f"Server: Using custom voice file: {speaker_wav_path}")
142
+ else:
143
+ print(f"Server: Voice '{voice}' not found in voices directory")
144
+
145
+ # Create a temporary file for output
146
+ with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as temp_file:
147
+ temp_output_path = temp_file.name
148
+
149
+ try:
150
+ # Generate audio using XTTS
151
+ if speaker_wav_path and os.path.exists(speaker_wav_path):
152
+ print(f"Server: Generating with speaker reference: {speaker_wav_path}")
164
153
  self.model.tts_to_file(
165
154
  text=text,
166
- speaker_wav=default_speaker,
155
+ speaker_wav=speaker_wav_path,
167
156
  language=language,
168
157
  file_path=temp_output_path
169
158
  )
170
159
  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
-
178
- # Read the generated audio file
179
- with open(temp_output_path, 'rb') as f:
180
- audio_bytes = f.read()
181
-
182
- print(f"Server: Generated {len(audio_bytes)} bytes of audio")
183
- return audio_bytes
160
+ print("Server: No valid speaker reference found, trying default")
161
+ # For XTTS without speaker reference, try to find a default
162
+ default_speaker = self._get_default_speaker_file()
163
+ if default_speaker and os.path.exists(default_speaker):
164
+ print(f"Server: Using default speaker: {default_speaker}")
165
+ self.model.tts_to_file(
166
+ text=text,
167
+ speaker_wav=default_speaker,
168
+ language=language,
169
+ file_path=temp_output_path
170
+ )
171
+ else:
172
+ # Create a more helpful error message
173
+ available_voices = self._get_all_available_voice_files()
174
+ error_msg = f"No speaker reference available. XTTS requires a speaker reference file.\n"
175
+ error_msg += f"Attempted to use: {speaker_wav_path if speaker_wav_path else 'None'}\n"
176
+ error_msg += f"Available voice files: {available_voices}"
177
+ raise RuntimeError(error_msg)
178
+
179
+ # Read the generated audio file
180
+ with open(temp_output_path, 'rb') as f:
181
+ audio_bytes = f.read()
182
+
183
+ print(f"Server: Generated {len(audio_bytes)} bytes of audio")
184
+ return audio_bytes
185
+
186
+ finally:
187
+ # Clean up temporary file
188
+ if os.path.exists(temp_output_path):
189
+ os.unlink(temp_output_path)
184
190
 
185
- finally:
186
- # Clean up temporary file
187
- if os.path.exists(temp_output_path):
188
- os.unlink(temp_output_path)
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 = []
191
+ except Exception as e:
192
+ print(f"Server: Error generating audio: {e}")
193
+ print(f"Server: Traceback:\n{traceback.format_exc()}")
194
+ raise
199
195
 
200
- if voices_dir.exists():
201
- voice_files = [str(f) for f in voices_dir.glob("*.wav")]
196
+ def _get_all_available_voice_files(self) -> List[str]:
197
+ """Get list of all available voice files for debugging"""
198
+ voices_dir = Path(__file__).parent / "voices"
199
+ voice_files = []
202
200
 
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"
201
+ if voices_dir.exists():
202
+ voice_files = [str(f) for f in voices_dir.glob("*.wav")]
203
+
204
+ return voice_files
208
205
 
209
- # Look for a default speaker file
210
- for filename in ["default.wav", "speaker.wav", "reference.wav"]:
211
- potential_path = voices_dir / filename
212
- if potential_path.exists():
213
- return str(potential_path)
206
+ def _get_default_speaker_file(self) -> Optional[str]:
207
+ """Get path to default speaker file"""
208
+ voices_dir = Path(__file__).parent / "voices"
209
+
210
+ # Look for a default speaker file
211
+ for filename in ["default.wav", "speaker.wav", "reference.wav"]:
212
+ potential_path = voices_dir / filename
213
+ if potential_path.exists():
214
+ return str(potential_path)
215
+
216
+ # If no default found, look for any wav file
217
+ wav_files = list(voices_dir.glob("*.wav"))
218
+ if wav_files:
219
+ return str(wav_files[0])
220
+
221
+ return None
214
222
 
215
- # If no default found, look for any wav file
216
- wav_files = list(voices_dir.glob("*.wav"))
217
- if wav_files:
218
- return str(wav_files[0])
223
+ def list_voices(self) -> List[str]:
224
+ """Return list of available voices"""
225
+ return self.available_voices
219
226
 
220
- return None
221
-
222
- def list_voices(self) -> List[str]:
223
- """Return list of available voices"""
224
- return self.available_voices
225
-
226
- def list_models(self) -> List[str]:
227
- """Return list of available models"""
228
- return self.available_models
227
+ def list_models(self) -> List[str]:
228
+ """Return list of available models"""
229
+ return self.available_models
229
230
 
230
- # --- Globals ---
231
- app = FastAPI(title="XTTS Server")
232
- router = APIRouter()
233
- xtts_server = XTTSServer()
234
- model_lock = asyncio.Lock() # Ensure thread-safe access
231
+ # --- Globals ---
232
+ app = FastAPI(title="XTTS Server")
233
+ router = APIRouter()
234
+ xtts_server = XTTSServer()
235
+ model_lock = asyncio.Lock() # Ensure thread-safe access
236
+
237
+ # --- API Endpoints ---
238
+ @router.post("/generate_audio")
239
+ async def generate_audio(request: GenerationRequest):
240
+ async with model_lock:
241
+ try:
242
+ audio_bytes = await xtts_server.generate_audio(
243
+ text=request.text,
244
+ voice=request.voice,
245
+ language=request.language,
246
+ speaker_wav=request.speaker_wav
247
+ )
248
+ from fastapi.responses import Response
249
+ return Response(content=audio_bytes, media_type="audio/wav")
250
+ except Exception as e:
251
+ print(f"Server: ERROR in generate_audio endpoint: {e}")
252
+ print(f"Server: ERROR traceback:\n{traceback.format_exc()}")
253
+ raise HTTPException(status_code=500, detail=str(e))
235
254
 
236
- # --- API Endpoints ---
237
- @router.post("/generate_audio")
238
- async def generate_audio(request: GenerationRequest):
239
- async with model_lock:
255
+ @router.get("/list_voices")
256
+ async def list_voices():
240
257
  try:
241
- audio_bytes = await xtts_server.generate_audio(
242
- text=request.text,
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")
258
+ voices = xtts_server.list_voices()
259
+ print(f"Server: Returning {len(voices)} voices: {voices}")
260
+ return {"voices": voices}
249
261
  except Exception as e:
250
- print(f"Server: ERROR in generate_audio endpoint: {e}")
262
+ print(f"Server: ERROR in list_voices endpoint: {e}")
251
263
  print(f"Server: ERROR traceback:\n{traceback.format_exc()}")
252
264
  raise HTTPException(status_code=500, detail=str(e))
253
265
 
254
- @router.get("/list_voices")
255
- async def list_voices():
256
- try:
257
- voices = xtts_server.list_voices()
258
- print(f"Server: Returning {len(voices)} voices: {voices}")
259
- return {"voices": voices}
260
- except Exception as e:
261
- print(f"Server: ERROR in list_voices endpoint: {e}")
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))
266
+ @router.get("/list_models")
267
+ async def list_models():
268
+ try:
269
+ models = xtts_server.list_models()
270
+ print(f"Server: Returning {len(models)} models: {models}")
271
+ return {"models": models}
272
+ except Exception as e:
273
+ print(f"Server: ERROR in list_models endpoint: {e}")
274
+ print(f"Server: ERROR traceback:\n{traceback.format_exc()}")
275
+ raise HTTPException(status_code=500, detail=str(e))
275
276
 
276
- @router.get("/status")
277
- async def status():
278
- return {
279
- "status": "running",
280
- "xtts_available": xtts_available,
281
- "model_loaded": xtts_server.model_loaded,
282
- "model_loading": xtts_server.model_loading,
283
- "voices_count": len(xtts_server.available_voices),
284
- "device": torch.cuda.get_device_name(0) if torch.cuda.is_available() else "CPU"
285
- }
277
+ @router.get("/status")
278
+ async def status():
279
+ return {
280
+ "status": "running",
281
+ "xtts_available": xtts_available,
282
+ "model_loaded": xtts_server.model_loaded,
283
+ "model_loading": xtts_server.model_loading,
284
+ "voices_count": len(xtts_server.available_voices),
285
+ "device": torch.cuda.get_device_name(0) if torch.cuda.is_available() else "CPU"
286
+ }
286
287
 
287
- # Add a health check endpoint that responds immediately
288
- @router.get("/health")
289
- async def health_check():
290
- return {"status": "healthy", "ready": True}
288
+ # Add a health check endpoint that responds immediately
289
+ @router.get("/health")
290
+ async def health_check():
291
+ return {"status": "healthy", "ready": True}
291
292
 
292
- app.include_router(router)
293
+ app.include_router(router)
293
294
 
294
- # --- Server Startup ---
295
- if __name__ == '__main__':
296
- parser = argparse.ArgumentParser(description="XTTS TTS Server")
297
- parser.add_argument("--host", type=str, default="localhost", help="Host to bind the server to.")
298
- parser.add_argument("--port", type=int, default="8081", help="Port to bind the server to.")
299
-
300
- args = parser.parse_args()
295
+ # --- Server Startup ---
296
+ if __name__ == '__main__':
297
+ parser = argparse.ArgumentParser(description="XTTS TTS Server")
298
+ parser.add_argument("--host", type=str, default="localhost", help="Host to bind the server to.")
299
+ parser.add_argument("--port", type=int, default="8081", help="Port to bind the server to.")
300
+
301
+ args = parser.parse_args()
301
302
 
302
- print(f"Server: Starting XTTS server on {args.host}:{args.port}")
303
- print(f"Server: XTTS available: {xtts_available}")
304
- print(f"Server: Model will be loaded on first audio generation request")
305
- print(f"Server: Available voices: {len(xtts_server.available_voices)}")
306
- if xtts_available:
307
- print(f"Server: Device: {torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'CPU'}")
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)
303
+ print(f"Server: Starting XTTS server on {args.host}:{args.port}")
304
+ print(f"Server: XTTS available: {xtts_available}")
305
+ print(f"Server: Model will be loaded on first audio generation request")
306
+ print(f"Server: Available voices: {len(xtts_server.available_voices)}")
307
+ if xtts_available:
308
+ print(f"Server: Device: {torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'CPU'}")
309
+
310
+ # Create voices directory if it doesn't exist
311
+ voices_dir = Path(__file__).parent / "voices"
312
+ voices_dir.mkdir(exist_ok=True)
313
+ print(f"Server: Voices directory: {voices_dir}")
314
+
315
+ uvicorn.run(app, host=args.host, port=args.port)
316
+ except Exception as e:
317
+ print(f"Server: CRITICAL ERROR during startup: {e}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lollms_client
3
- Version: 1.6.1
3
+ Version: 1.6.2
4
4
  Summary: A client library for LoLLMs generate endpoint
5
5
  Author-email: ParisNeo <parisneoai@gmail.com>
6
6
  License: Apache License
@@ -1302,6 +1302,7 @@ try:
1302
1302
  except Exception as e:
1303
1303
  ASCIIColors.error(f"Error initializing Hugging Face Inference API binding: {e}")
1304
1304
  ASCIIColors.info("Please ensure your Hugging Face API token is correctly set and you have access to the specified model.")```
1305
+ ```
1305
1306
 
1306
1307
  ---
1307
1308
 
@@ -1403,7 +1404,9 @@ else:
1403
1404
 
1404
1405
  except Exception as e:
1405
1406
  ASCIIColors.error(f"An error occurred during multi-image fusion: {e}")
1406
- ```This powerful feature allows for complex creative tasks like character swapping, background replacement, and style transfer directly through the `lollms_client` library.
1407
+ ```
1408
+
1409
+ This powerful feature allows for complex creative tasks like character swapping, background replacement, and style transfer directly through the `lollms_client` library.
1407
1410
 
1408
1411
  ### Listing Available Models
1409
1412
 
@@ -1,7 +1,7 @@
1
- lollms_client/__init__.py,sha256=lRGg7_mob01b8ZxJtc7S4djDcL7d8kA6nJfKHSIuOWY,1146
1
+ lollms_client/__init__.py,sha256=zZKNRiai-0QTZCCLS1KlMYvwRdcIEla7HeypZZBJUTs,1146
2
2
  lollms_client/lollms_agentic.py,sha256=pQiMEuB_XkG29-SW6u4KTaMFPr6eKqacInggcCuCW3k,13914
3
3
  lollms_client/lollms_config.py,sha256=goEseDwDxYJf3WkYJ4IrLXwg3Tfw73CXV2Avg45M_hE,21876
4
- lollms_client/lollms_core.py,sha256=lYsDsVr_SWhFhMiFxNzzY_ZTvNvFZAUdvbxQRpO9EcI,321464
4
+ lollms_client/lollms_core.py,sha256=TsyOvjtZtC-C2cy3-6Zag7QW-UfUN3jzfStgMsqfte4,321724
5
5
  lollms_client/lollms_discussion.py,sha256=LZc9jYbUMRTovehiFJKEp-NXuCl_WnrqUtT3t4Nzayk,123922
6
6
  lollms_client/lollms_js_analyzer.py,sha256=01zUvuO2F_lnUe_0NLxe1MF5aHE1hO8RZi48mNPv-aw,8361
7
7
  lollms_client/lollms_llm_binding.py,sha256=tXuc3gxe6UrP36OBGsR-ESvQ9LpsB_nqtqL-GsEj6Uc,25019
@@ -12,7 +12,7 @@ lollms_client/lollms_python_analyzer.py,sha256=7gf1fdYgXCOkPUkBAPNmr6S-66hMH4_Ko
12
12
  lollms_client/lollms_stt_binding.py,sha256=jAUhLouEhh2hmm1bK76ianfw_6B59EHfY3FmLv6DU-g,5111
13
13
  lollms_client/lollms_tti_binding.py,sha256=B38nzBCSPV9jVRZa-x8W7l9nJEW0RyS1MMJoueb8kt0,8519
14
14
  lollms_client/lollms_ttm_binding.py,sha256=FjVVSNXOZXK1qvcKEfxdiX6l2b4XdGOSNnZ0utAsbDg,4167
15
- lollms_client/lollms_tts_binding.py,sha256=4qw94lc9M8lsh2q1u3FF0RuxTY__kukYg266ai3or-Y,5126
15
+ lollms_client/lollms_tts_binding.py,sha256=k13rNq4YmuR50kkAEacwADW7COoDUOMLGAcnm27xjO4,5150
16
16
  lollms_client/lollms_ttv_binding.py,sha256=KkTaHLBhEEdt4sSVBlbwr5i_g_TlhcrwrT-7DjOsjWQ,4131
17
17
  lollms_client/lollms_types.py,sha256=0iSH1QHRRD-ddBqoL9EEKJ8wWCuwDUlN_FrfbCdg7Lw,3522
18
18
  lollms_client/lollms_utilities.py,sha256=3DAsII2X9uhRzRL-D0QlALcEdRg82y7OIL4yHVF32gY,19446
@@ -75,13 +75,13 @@ lollms_client/tts_bindings/piper_tts/__init__.py,sha256=FbMw_m2QOn2ny7r5El_s6jBy
75
75
  lollms_client/tts_bindings/piper_tts/server/install_piper.py,sha256=g71Ne2T18wAytOPipfQ9DNeTAOD9PrII5qC-vr9DtLA,3256
76
76
  lollms_client/tts_bindings/piper_tts/server/main.py,sha256=DMozfSR1aCbrlmOXltRFjtXhYhXajsGcNKQjsWgRwZk,17402
77
77
  lollms_client/tts_bindings/piper_tts/server/setup_voices.py,sha256=UdHaPa5aNcw8dR-aRGkZr2OfSFFejH79lXgfwT0P3ss,1964
78
- lollms_client/tts_bindings/xtts/__init__.py,sha256=i1bU6D7wDnWOSf7TRrw_bqeAF6Y8eFp-JIW8z_lWuQM,4211
79
- lollms_client/tts_bindings/xtts/server/main.py,sha256=T-Kn5NM-u1FJMygeV8rOoZKlqnYHZM30TsnbyZNDzxo,12794
78
+ lollms_client/tts_bindings/xtts/__init__.py,sha256=2a1qzF8iDbr1bt0KQhcNbZqy0B06gjsWVAMJF-YATrE,6371
79
+ lollms_client/tts_bindings/xtts/server/main.py,sha256=27l5FlclhqiXSBf-_qg4QR7Q7mUly72kaBtG8kBXASE,14088
80
80
  lollms_client/tts_bindings/xtts/server/setup_voices.py,sha256=UdHaPa5aNcw8dR-aRGkZr2OfSFFejH79lXgfwT0P3ss,1964
81
81
  lollms_client/ttv_bindings/__init__.py,sha256=UZ8o2izQOJLQgtZ1D1cXoNST7rzqW22rL2Vufc7ddRc,3141
82
82
  lollms_client/ttv_bindings/lollms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
83
- lollms_client-1.6.1.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
84
- lollms_client-1.6.1.dist-info/METADATA,sha256=wRlmSzWMv4IsGALozvPDg6CIiEzqeQb9qrgj99hMvyo,76825
85
- lollms_client-1.6.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
86
- lollms_client-1.6.1.dist-info/top_level.txt,sha256=Bk_kz-ri6Arwsk7YG-T5VsRorV66uVhcHGvb_g2WqgE,14
87
- lollms_client-1.6.1.dist-info/RECORD,,
83
+ lollms_client-1.6.2.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
84
+ lollms_client-1.6.2.dist-info/METADATA,sha256=g8ACOGQCyj4dIDIfa3fJmyqJzoLB-aN2U_qkOKs7CVw,76834
85
+ lollms_client-1.6.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
86
+ lollms_client-1.6.2.dist-info/top_level.txt,sha256=Bk_kz-ri6Arwsk7YG-T5VsRorV66uVhcHGvb_g2WqgE,14
87
+ lollms_client-1.6.2.dist-info/RECORD,,