lollms-client 1.4.1__py3-none-any.whl → 1.7.10__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.
- lollms_client/__init__.py +1 -1
- lollms_client/llm_bindings/azure_openai/__init__.py +2 -2
- lollms_client/llm_bindings/claude/__init__.py +125 -34
- lollms_client/llm_bindings/gemini/__init__.py +261 -159
- lollms_client/llm_bindings/grok/__init__.py +52 -14
- lollms_client/llm_bindings/groq/__init__.py +2 -2
- lollms_client/llm_bindings/hugging_face_inference_api/__init__.py +2 -2
- lollms_client/llm_bindings/litellm/__init__.py +1 -1
- lollms_client/llm_bindings/llamacpp/__init__.py +18 -11
- lollms_client/llm_bindings/lollms/__init__.py +151 -32
- lollms_client/llm_bindings/lollms_webui/__init__.py +1 -1
- lollms_client/llm_bindings/mistral/__init__.py +2 -2
- lollms_client/llm_bindings/novita_ai/__init__.py +439 -0
- lollms_client/llm_bindings/ollama/__init__.py +309 -93
- lollms_client/llm_bindings/open_router/__init__.py +2 -2
- lollms_client/llm_bindings/openai/__init__.py +148 -29
- lollms_client/llm_bindings/openllm/__init__.py +362 -506
- lollms_client/llm_bindings/openwebui/__init__.py +465 -0
- lollms_client/llm_bindings/perplexity/__init__.py +326 -0
- lollms_client/llm_bindings/pythonllamacpp/__init__.py +3 -3
- lollms_client/llm_bindings/tensor_rt/__init__.py +1 -1
- lollms_client/llm_bindings/transformers/__init__.py +428 -632
- lollms_client/llm_bindings/vllm/__init__.py +1 -1
- lollms_client/lollms_agentic.py +4 -2
- lollms_client/lollms_base_binding.py +61 -0
- lollms_client/lollms_core.py +516 -1890
- lollms_client/lollms_discussion.py +55 -18
- lollms_client/lollms_llm_binding.py +112 -261
- lollms_client/lollms_mcp_binding.py +34 -75
- lollms_client/lollms_personality.py +5 -2
- lollms_client/lollms_stt_binding.py +85 -52
- lollms_client/lollms_tti_binding.py +23 -37
- lollms_client/lollms_ttm_binding.py +24 -42
- lollms_client/lollms_tts_binding.py +28 -17
- lollms_client/lollms_ttv_binding.py +24 -42
- lollms_client/lollms_types.py +4 -2
- lollms_client/stt_bindings/whisper/__init__.py +108 -23
- lollms_client/stt_bindings/whispercpp/__init__.py +7 -1
- lollms_client/tti_bindings/diffusers/__init__.py +418 -810
- lollms_client/tti_bindings/diffusers/server/main.py +1051 -0
- lollms_client/tti_bindings/gemini/__init__.py +182 -239
- lollms_client/tti_bindings/leonardo_ai/__init__.py +127 -0
- lollms_client/tti_bindings/lollms/__init__.py +4 -1
- lollms_client/tti_bindings/novita_ai/__init__.py +105 -0
- lollms_client/tti_bindings/openai/__init__.py +10 -11
- lollms_client/tti_bindings/stability_ai/__init__.py +178 -0
- lollms_client/ttm_bindings/audiocraft/__init__.py +7 -12
- lollms_client/ttm_bindings/beatoven_ai/__init__.py +129 -0
- lollms_client/ttm_bindings/lollms/__init__.py +4 -17
- lollms_client/ttm_bindings/replicate/__init__.py +115 -0
- lollms_client/ttm_bindings/stability_ai/__init__.py +117 -0
- lollms_client/ttm_bindings/topmediai/__init__.py +96 -0
- lollms_client/tts_bindings/bark/__init__.py +7 -10
- lollms_client/tts_bindings/lollms/__init__.py +6 -1
- lollms_client/tts_bindings/piper_tts/__init__.py +8 -11
- lollms_client/tts_bindings/xtts/__init__.py +157 -74
- lollms_client/tts_bindings/xtts/server/main.py +241 -280
- {lollms_client-1.4.1.dist-info → lollms_client-1.7.10.dist-info}/METADATA +316 -6
- lollms_client-1.7.10.dist-info/RECORD +89 -0
- lollms_client/ttm_bindings/bark/__init__.py +0 -339
- lollms_client-1.4.1.dist-info/RECORD +0 -78
- {lollms_client-1.4.1.dist-info → lollms_client-1.7.10.dist-info}/WHEEL +0 -0
- {lollms_client-1.4.1.dist-info → lollms_client-1.7.10.dist-info}/licenses/LICENSE +0 -0
- {lollms_client-1.4.1.dist-info → lollms_client-1.7.10.dist-info}/top_level.txt +0 -0
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
from abc import
|
|
1
|
+
from abc import abstractmethod
|
|
2
2
|
import importlib
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from typing import Optional, List, Dict, Any, Union
|
|
5
5
|
import yaml
|
|
6
6
|
from ascii_colors import trace_exception
|
|
7
|
+
from lollms_client.lollms_base_binding import LollmsBaseBinding
|
|
7
8
|
|
|
8
|
-
class LollmsTTSBinding(
|
|
9
|
+
class LollmsTTSBinding(LollmsBaseBinding):
|
|
9
10
|
def __init__(self,
|
|
10
11
|
binding_name: str = "unknown",
|
|
11
12
|
**kwargs):
|
|
12
|
-
|
|
13
|
+
super().__init__(binding_name=binding_name, **kwargs)
|
|
13
14
|
self.settings = kwargs
|
|
14
15
|
|
|
15
16
|
@abstractmethod
|
|
@@ -49,26 +50,21 @@ class LollmsTTSBindingManager:
|
|
|
49
50
|
except Exception as e:
|
|
50
51
|
trace_exception(e)
|
|
51
52
|
print(f"Failed to load TTS binding {binding_name}: {str(e)}")
|
|
52
|
-
|
|
53
|
-
def create_binding(self,
|
|
53
|
+
def create_binding(self,
|
|
54
54
|
binding_name: str,
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
**kwargs) -> Optional[LollmsTTSBinding]:
|
|
56
|
+
"""
|
|
57
|
+
Create an instance of a specific binding.
|
|
58
|
+
"""
|
|
59
59
|
if binding_name not in self.available_bindings:
|
|
60
60
|
self._load_binding(binding_name)
|
|
61
|
-
|
|
61
|
+
|
|
62
62
|
binding_class = self.available_bindings.get(binding_name)
|
|
63
63
|
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
|
|
64
|
+
return binding_class(binding_name=binding_name, **kwargs)
|
|
70
65
|
return None
|
|
71
66
|
|
|
67
|
+
|
|
72
68
|
@staticmethod
|
|
73
69
|
def _get_fallback_description(binding_name: str) -> Dict:
|
|
74
70
|
return {
|
|
@@ -124,4 +120,19 @@ class LollmsTTSBindingManager:
|
|
|
124
120
|
def get_available_bindings(tts_bindings_dir: Union[str, Path] = None) -> List[Dict]:
|
|
125
121
|
if tts_bindings_dir is None:
|
|
126
122
|
tts_bindings_dir = Path(__file__).resolve().parent / "tts_bindings"
|
|
127
|
-
return LollmsTTSBindingManager.get_bindings_list(tts_bindings_dir)
|
|
123
|
+
return LollmsTTSBindingManager.get_bindings_list(tts_bindings_dir)
|
|
124
|
+
|
|
125
|
+
def list_binding_models(stt_binding_name: str, stt_binding_config: Optional[Dict[str, any]]|None = None, stt_bindings_dir: str|Path = Path(__file__).parent / "stt_bindings") -> List[Dict]:
|
|
126
|
+
"""
|
|
127
|
+
Lists all available models for a specific binding.
|
|
128
|
+
"""
|
|
129
|
+
binding = LollmsTTSBindingManager(stt_bindings_dir).create_binding(
|
|
130
|
+
binding_name=stt_binding_name,
|
|
131
|
+
**{
|
|
132
|
+
k: v
|
|
133
|
+
for k, v in (stt_binding_config or {}).items()
|
|
134
|
+
if k != "binding_name"
|
|
135
|
+
}
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
return binding.list_models() if binding else []
|
|
@@ -1,37 +1,26 @@
|
|
|
1
1
|
# lollms_client/lollms_ttv_binding.py
|
|
2
|
-
from abc import
|
|
2
|
+
from abc import abstractmethod
|
|
3
3
|
import importlib
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from typing import Optional, List, Dict, Any, Union
|
|
6
6
|
from ascii_colors import trace_exception
|
|
7
|
+
from lollms_client.lollms_base_binding import LollmsBaseBinding
|
|
7
8
|
|
|
8
|
-
class LollmsTTVBinding(
|
|
9
|
+
class LollmsTTVBinding(LollmsBaseBinding):
|
|
9
10
|
"""Abstract base class for all LOLLMS Text-to-Video bindings."""
|
|
10
11
|
|
|
11
12
|
def __init__(self,
|
|
12
|
-
binding_name:str="unknown"
|
|
13
|
+
binding_name:str="unknown",
|
|
14
|
+
**kwargs):
|
|
13
15
|
"""
|
|
14
16
|
Initialize the LollmsTTVBinding base class.
|
|
15
|
-
|
|
16
|
-
Args:
|
|
17
|
-
binding_name (Optional[str]): The binding name
|
|
18
17
|
"""
|
|
19
|
-
|
|
18
|
+
super().__init__(binding_name=binding_name, **kwargs)
|
|
20
19
|
|
|
21
20
|
@abstractmethod
|
|
22
21
|
def generate_video(self, prompt: str, **kwargs) -> bytes:
|
|
23
22
|
"""
|
|
24
23
|
Generates video data from the provided text prompt.
|
|
25
|
-
|
|
26
|
-
Args:
|
|
27
|
-
prompt (str): The text prompt describing the desired video content.
|
|
28
|
-
**kwargs: Additional binding-specific parameters (e.g., duration, fps, style, seed).
|
|
29
|
-
|
|
30
|
-
Returns:
|
|
31
|
-
bytes: The generated video data (e.g., in MP4 format).
|
|
32
|
-
|
|
33
|
-
Raises:
|
|
34
|
-
Exception: If video generation fails.
|
|
35
24
|
"""
|
|
36
25
|
pass
|
|
37
26
|
|
|
@@ -39,12 +28,6 @@ class LollmsTTVBinding(ABC):
|
|
|
39
28
|
def list_models(self, **kwargs) -> List[str]:
|
|
40
29
|
"""
|
|
41
30
|
Lists the available TTV models or services supported by the binding.
|
|
42
|
-
|
|
43
|
-
Args:
|
|
44
|
-
**kwargs: Additional binding-specific parameters.
|
|
45
|
-
|
|
46
|
-
Returns:
|
|
47
|
-
List[str]: A list of available model/service identifiers.
|
|
48
31
|
"""
|
|
49
32
|
pass
|
|
50
33
|
|
|
@@ -52,13 +35,6 @@ class LollmsTTVBindingManager:
|
|
|
52
35
|
"""Manages TTV binding discovery and instantiation."""
|
|
53
36
|
|
|
54
37
|
def __init__(self, ttv_bindings_dir: Union[str, Path] = Path(__file__).parent.parent / "ttv_bindings"):
|
|
55
|
-
"""
|
|
56
|
-
Initialize the LollmsTTVBindingManager.
|
|
57
|
-
|
|
58
|
-
Args:
|
|
59
|
-
ttv_bindings_dir (Union[str, Path]): Directory containing TTV binding implementations.
|
|
60
|
-
Defaults to the "ttv_bindings" subdirectory.
|
|
61
|
-
"""
|
|
62
38
|
self.ttv_bindings_dir = Path(ttv_bindings_dir)
|
|
63
39
|
self.available_bindings = {}
|
|
64
40
|
|
|
@@ -79,13 +55,6 @@ class LollmsTTVBindingManager:
|
|
|
79
55
|
**kwargs) -> Optional[LollmsTTVBinding]:
|
|
80
56
|
"""
|
|
81
57
|
Create an instance of a specific TTV binding.
|
|
82
|
-
|
|
83
|
-
Args:
|
|
84
|
-
binding_name (str): Name of the TTV binding to create.
|
|
85
|
-
**kwargs: Additional parameters specific to the binding's __init__.
|
|
86
|
-
|
|
87
|
-
Returns:
|
|
88
|
-
Optional[LollmsTTVBinding]: Binding instance or None if creation failed.
|
|
89
58
|
"""
|
|
90
59
|
if binding_name not in self.available_bindings:
|
|
91
60
|
self._load_binding(binding_name)
|
|
@@ -93,7 +62,7 @@ class LollmsTTVBindingManager:
|
|
|
93
62
|
binding_class = self.available_bindings.get(binding_name)
|
|
94
63
|
if binding_class:
|
|
95
64
|
try:
|
|
96
|
-
return binding_class(**kwargs)
|
|
65
|
+
return binding_class(binding_name=binding_name, **kwargs)
|
|
97
66
|
except Exception as e:
|
|
98
67
|
trace_exception(e)
|
|
99
68
|
print(f"Failed to instantiate TTV binding {binding_name}: {str(e)}")
|
|
@@ -103,9 +72,22 @@ class LollmsTTVBindingManager:
|
|
|
103
72
|
def get_available_bindings(self) -> list[str]:
|
|
104
73
|
"""
|
|
105
74
|
Return list of available TTV binding names based on subdirectories.
|
|
106
|
-
|
|
107
|
-
Returns:
|
|
108
|
-
list[str]: List of binding names.
|
|
109
75
|
"""
|
|
110
76
|
return [binding_dir.name for binding_dir in self.ttv_bindings_dir.iterdir()
|
|
111
|
-
if binding_dir.is_dir() and (binding_dir / "__init__.py").exists()]
|
|
77
|
+
if binding_dir.is_dir() and (binding_dir / "__init__.py").exists()]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def list_binding_models(ttv_binding_name: str, ttv_binding_config: Optional[Dict[str, any]]|None = None, ttv_bindings_dir: str|Path = Path(__file__).parent / "ttv_bindings") -> List[Dict]:
|
|
81
|
+
"""
|
|
82
|
+
Lists all available models for a specific binding.
|
|
83
|
+
"""
|
|
84
|
+
binding = LollmsTTVBindingManager(ttv_bindings_dir).create_binding(
|
|
85
|
+
binding_name=ttv_binding_name,
|
|
86
|
+
**{
|
|
87
|
+
k: v
|
|
88
|
+
for k, v in (ttv_binding_config or {}).items()
|
|
89
|
+
if k != "binding_name"
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
return binding.list_models() if binding else []
|
lollms_client/lollms_types.py
CHANGED
|
@@ -41,8 +41,10 @@ class MSG_TYPE(Enum):
|
|
|
41
41
|
MSG_TYPE_OBSERVATION = 23# the ai shows its reasoning
|
|
42
42
|
|
|
43
43
|
MSG_TYPE_ERROR = 24#a severe error hapened
|
|
44
|
-
MSG_TYPE_GENERATING_TITLE_START = 25#
|
|
45
|
-
MSG_TYPE_GENERATING_TITLE_END = 26#
|
|
44
|
+
MSG_TYPE_GENERATING_TITLE_START = 25#title generation started
|
|
45
|
+
MSG_TYPE_GENERATING_TITLE_END = 26#title generation done
|
|
46
|
+
|
|
47
|
+
MSG_TYPE_SOURCES_LIST = 27# List of sources provided
|
|
46
48
|
|
|
47
49
|
|
|
48
50
|
class SENDER_TYPES(Enum):
|
|
@@ -3,6 +3,8 @@ import os
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from typing import Optional, List, Union, Dict, Any
|
|
5
5
|
from ascii_colors import trace_exception, ASCIIColors
|
|
6
|
+
import time
|
|
7
|
+
import filelock
|
|
6
8
|
|
|
7
9
|
# --- Package Management and Conditional Imports ---
|
|
8
10
|
_whisper_installed = False
|
|
@@ -25,7 +27,7 @@ try:
|
|
|
25
27
|
preferred_torch_device_for_install = "mps" # or keep cpu if mps detection is later
|
|
26
28
|
|
|
27
29
|
torch_pkgs = ["torch", "torchaudio","xformers"]
|
|
28
|
-
audiocraft_core_pkgs = ["openai-whisper"]
|
|
30
|
+
audiocraft_core_pkgs = ["openai-whisper", "filelock"]
|
|
29
31
|
|
|
30
32
|
torch_index_url = None
|
|
31
33
|
if preferred_torch_device_for_install == "cuda":
|
|
@@ -67,7 +69,7 @@ class WhisperSTTBinding(LollmsSTTBinding):
|
|
|
67
69
|
"""
|
|
68
70
|
|
|
69
71
|
# Standard Whisper model sizes
|
|
70
|
-
WHISPER_MODEL_SIZES = ["
|
|
72
|
+
WHISPER_MODEL_SIZES = ["turbo"]
|
|
71
73
|
|
|
72
74
|
def __init__(self,
|
|
73
75
|
**kwargs # To catch any other LollmsSTTBinding standard args
|
|
@@ -76,18 +78,19 @@ class WhisperSTTBinding(LollmsSTTBinding):
|
|
|
76
78
|
Initialize the Whisper STT binding.
|
|
77
79
|
|
|
78
80
|
Args:
|
|
79
|
-
model_name (str): The Whisper model size to use (e.g., "
|
|
80
|
-
Defaults to "
|
|
81
|
+
model_name (str): The Whisper model size to use (e.g., "turbo").
|
|
82
|
+
Defaults to "turno".
|
|
81
83
|
device (Optional[str]): The device to run the model on ("cpu", "cuda", "mps").
|
|
82
84
|
If None, `torch` will attempt to auto-detect. Defaults to None.
|
|
83
85
|
"""
|
|
84
86
|
super().__init__(binding_name="whisper") # Not applicable
|
|
87
|
+
self.default_model_name = kwargs.get("model_name", "turbo")
|
|
85
88
|
|
|
86
89
|
if not _whisper_installed:
|
|
87
90
|
raise ImportError(f"Whisper STT binding dependencies not met. Please ensure 'openai-whisper' and 'torch' are installed. Error: {_whisper_installation_error}")
|
|
88
91
|
|
|
89
|
-
self.device = kwargs.get("device",None)
|
|
90
|
-
if self.device is None: # Auto-detect if not specified
|
|
92
|
+
self.device = kwargs.get("device", None)
|
|
93
|
+
if self.device is None or self.device == "": # Auto-detect if not specified or empty
|
|
91
94
|
if torch.cuda.is_available():
|
|
92
95
|
self.device = "cuda"
|
|
93
96
|
elif hasattr(torch.backends, 'mps') and torch.backends.mps.is_available(): # For Apple Silicon
|
|
@@ -95,37 +98,115 @@ class WhisperSTTBinding(LollmsSTTBinding):
|
|
|
95
98
|
else:
|
|
96
99
|
self.device = "cpu"
|
|
97
100
|
|
|
101
|
+
# Validate device string
|
|
102
|
+
if not self.device or self.device.strip() == "":
|
|
103
|
+
ASCIIColors.warning("Device was empty or invalid, defaulting to 'cpu'")
|
|
104
|
+
self.device = "cpu"
|
|
105
|
+
|
|
98
106
|
ASCIIColors.info(f"WhisperSTTBinding: Using device '{self.device}'.")
|
|
99
107
|
|
|
100
108
|
self.loaded_model_name = None
|
|
101
109
|
self.model = None
|
|
102
|
-
|
|
110
|
+
try:
|
|
111
|
+
self._load_whisper_model(kwargs.get("model_name", "turbo")) # Default to "turbo" if not specified
|
|
112
|
+
except Exception as e:
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _get_whisper_cache_dir(self) -> Path:
|
|
117
|
+
"""Get the Whisper cache directory."""
|
|
118
|
+
# Whisper uses the same cache location as specified in its code
|
|
119
|
+
cache_dir = os.getenv("XDG_CACHE_HOME", os.path.expanduser("~/.cache"))
|
|
120
|
+
return Path(cache_dir) / "whisper"
|
|
103
121
|
|
|
122
|
+
def _is_model_downloaded(self, model_name: str) -> bool:
|
|
123
|
+
"""Check if a Whisper model is already downloaded and valid."""
|
|
124
|
+
cache_dir = self._get_whisper_cache_dir()
|
|
125
|
+
# Whisper models are stored as {model_name}.pt
|
|
126
|
+
model_file = cache_dir / f"{model_name}.pt"
|
|
127
|
+
|
|
128
|
+
if not model_file.exists():
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
# Check if file size is reasonable (at least 1MB to catch partial downloads)
|
|
132
|
+
try:
|
|
133
|
+
file_size = model_file.stat().st_size
|
|
134
|
+
if file_size < 1_000_000: # Less than 1MB is likely corrupted
|
|
135
|
+
ASCIIColors.warning(f"Model file {model_file} appears corrupted (size: {file_size} bytes). Will re-download.")
|
|
136
|
+
return False
|
|
137
|
+
except Exception as e:
|
|
138
|
+
ASCIIColors.warning(f"Could not check model file size: {e}")
|
|
139
|
+
return False
|
|
140
|
+
|
|
141
|
+
return True
|
|
104
142
|
|
|
105
143
|
def _load_whisper_model(self, model_name_to_load: str):
|
|
106
|
-
"""Loads or reloads the Whisper model."""
|
|
107
|
-
if model_name_to_load not in
|
|
144
|
+
"""Loads or reloads the Whisper model with file locking to prevent concurrent downloads."""
|
|
145
|
+
if model_name_to_load not in whisper.available_models():
|
|
108
146
|
ASCIIColors.warning(f"'{model_name_to_load}' is not a standard Whisper model size. Attempting to load anyway. Known sizes: {self.WHISPER_MODEL_SIZES}")
|
|
109
147
|
|
|
110
148
|
if self.model is not None and self.loaded_model_name == model_name_to_load:
|
|
111
149
|
ASCIIColors.info(f"Whisper model '{model_name_to_load}' already loaded.")
|
|
112
150
|
return
|
|
113
151
|
|
|
152
|
+
# Get the cache directory and create lock file path
|
|
153
|
+
cache_dir = self._get_whisper_cache_dir()
|
|
154
|
+
cache_dir.mkdir(parents=True, exist_ok=True)
|
|
155
|
+
lock_file = cache_dir / f"{model_name_to_load}.lock"
|
|
156
|
+
model_file = cache_dir / f"{model_name_to_load}.pt"
|
|
157
|
+
|
|
114
158
|
ASCIIColors.info(f"Loading Whisper model: '{model_name_to_load}' on device '{self.device}'...")
|
|
159
|
+
|
|
160
|
+
# Use file lock to prevent concurrent downloads
|
|
161
|
+
lock = filelock.FileLock(lock_file, timeout=300) # 5 minute timeout
|
|
162
|
+
|
|
115
163
|
try:
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
164
|
+
with lock:
|
|
165
|
+
# Check if model is already downloaded and valid
|
|
166
|
+
if self._is_model_downloaded(model_name_to_load):
|
|
167
|
+
ASCIIColors.info(f"Model '{model_name_to_load}' already downloaded, loading from cache...")
|
|
168
|
+
else:
|
|
169
|
+
ASCIIColors.info(f"Downloading model '{model_name_to_load}' (this process has the lock)...")
|
|
170
|
+
# If model file exists but is corrupted, delete it first
|
|
171
|
+
if model_file.exists():
|
|
172
|
+
try:
|
|
173
|
+
ASCIIColors.warning(f"Removing corrupted model file: {model_file}")
|
|
174
|
+
model_file.unlink()
|
|
175
|
+
except Exception as e:
|
|
176
|
+
ASCIIColors.error(f"Could not remove corrupted model file: {e}")
|
|
177
|
+
|
|
178
|
+
# Load the model (will download if not cached)
|
|
179
|
+
self.model = whisper.load_model(model_name_to_load, device=self.device)
|
|
180
|
+
self.loaded_model_name = model_name_to_load
|
|
181
|
+
self.model_name = model_name_to_load
|
|
182
|
+
ASCIIColors.green(f"Whisper model '{model_name_to_load}' loaded successfully.")
|
|
183
|
+
|
|
184
|
+
except filelock.Timeout:
|
|
185
|
+
error_msg = f"Timeout waiting for model '{model_name_to_load}' download lock. Another process may be downloading."
|
|
186
|
+
ASCIIColors.error(error_msg)
|
|
187
|
+
self.model = None
|
|
188
|
+
self.loaded_model_name = None
|
|
189
|
+
raise RuntimeError(error_msg)
|
|
122
190
|
except Exception as e:
|
|
123
191
|
self.model = None
|
|
124
192
|
self.loaded_model_name = None
|
|
125
193
|
ASCIIColors.error(f"Failed to load Whisper model '{model_name_to_load}': {e}")
|
|
126
194
|
trace_exception(e)
|
|
127
|
-
#
|
|
195
|
+
# If loading failed, try to clean up potentially corrupted file
|
|
196
|
+
if model_file.exists():
|
|
197
|
+
try:
|
|
198
|
+
ASCIIColors.warning(f"Cleaning up potentially corrupted model file after error...")
|
|
199
|
+
model_file.unlink()
|
|
200
|
+
except Exception as cleanup_error:
|
|
201
|
+
ASCIIColors.error(f"Could not clean up model file: {cleanup_error}")
|
|
128
202
|
raise RuntimeError(f"Failed to load Whisper model '{model_name_to_load}'") from e
|
|
203
|
+
finally:
|
|
204
|
+
# Clean up lock file if it exists and is not locked
|
|
205
|
+
try:
|
|
206
|
+
if lock_file.exists() and not lock.is_locked:
|
|
207
|
+
lock_file.unlink()
|
|
208
|
+
except Exception:
|
|
209
|
+
pass # Ignore cleanup errors
|
|
129
210
|
|
|
130
211
|
|
|
131
212
|
def transcribe_audio(self, audio_path: Union[str, Path], model: Optional[str] = None, **kwargs) -> str:
|
|
@@ -149,6 +230,10 @@ class WhisperSTTBinding(LollmsSTTBinding):
|
|
|
149
230
|
RuntimeError: If the Whisper model is not loaded or transcription fails.
|
|
150
231
|
Exception: For other errors during transcription.
|
|
151
232
|
"""
|
|
233
|
+
if not self.model:
|
|
234
|
+
self._load_whisper_model(self.default_model_name)
|
|
235
|
+
|
|
236
|
+
|
|
152
237
|
audio_file = Path(audio_path)
|
|
153
238
|
if not audio_file.exists():
|
|
154
239
|
raise FileNotFoundError(f"Audio file not found at: {audio_path}")
|
|
@@ -191,8 +276,8 @@ class WhisperSTTBinding(LollmsSTTBinding):
|
|
|
191
276
|
trace_exception(e)
|
|
192
277
|
raise Exception(f"Whisper transcription error: {e}") from e
|
|
193
278
|
|
|
194
|
-
|
|
195
|
-
def list_models(
|
|
279
|
+
@staticmethod
|
|
280
|
+
def list_models(**kwargs) -> List[str]:
|
|
196
281
|
"""
|
|
197
282
|
Lists the available standard Whisper model sizes.
|
|
198
283
|
|
|
@@ -202,7 +287,7 @@ class WhisperSTTBinding(LollmsSTTBinding):
|
|
|
202
287
|
Returns:
|
|
203
288
|
List[str]: A list of available Whisper model size identifiers.
|
|
204
289
|
"""
|
|
205
|
-
return
|
|
290
|
+
return whisper.available_models() # Return a copy
|
|
206
291
|
|
|
207
292
|
def __del__(self):
|
|
208
293
|
"""Clean up: Unload the model to free resources."""
|
|
@@ -252,7 +337,7 @@ if __name__ == '__main__':
|
|
|
252
337
|
else:
|
|
253
338
|
try:
|
|
254
339
|
ASCIIColors.cyan("\n--- Initializing WhisperSTTBinding (model: 'tiny') ---")
|
|
255
|
-
# Using 'tiny' model for faster testing. Change to '
|
|
340
|
+
# Using 'tiny' model for faster testing. Change to 'turbo' or 'small' for better quality.
|
|
256
341
|
stt_binding = WhisperSTTBinding(model_name="tiny")
|
|
257
342
|
|
|
258
343
|
ASCIIColors.cyan("\n--- Listing available Whisper models ---")
|
|
@@ -269,9 +354,9 @@ if __name__ == '__main__':
|
|
|
269
354
|
# print(f"Transcription (tiny, lang='en'): '{transcription_lang_hint}'")
|
|
270
355
|
|
|
271
356
|
# Test switching model dynamically (optional, will re-download/load if different)
|
|
272
|
-
# ASCIIColors.cyan(f"\n--- Transcribing '{test_audio_file.name}' by switching to '
|
|
273
|
-
# transcription_base = stt_binding.transcribe_audio(test_audio_file, model="
|
|
274
|
-
# print(f"Transcription (
|
|
357
|
+
# ASCIIColors.cyan(f"\n--- Transcribing '{test_audio_file.name}' by switching to 'turbo' model ---")
|
|
358
|
+
# transcription_base = stt_binding.transcribe_audio(test_audio_file, model="turbo")
|
|
359
|
+
# print(f"Transcription (turbo): '{transcription_base}'")
|
|
275
360
|
|
|
276
361
|
|
|
277
362
|
except ImportError as e_imp:
|
|
@@ -31,6 +31,8 @@ class WhisperCppSTTBinding(LollmsSTTBinding):
|
|
|
31
31
|
n_threads = kwargs.get("n_threads", 4)
|
|
32
32
|
extra_whisper_args = kwargs.get("extra_whisper_args", []) # e.g. ["--no-timestamps"]
|
|
33
33
|
|
|
34
|
+
self.default_model_name = "base"
|
|
35
|
+
|
|
34
36
|
# --- Validate FFMPEG ---
|
|
35
37
|
self.ffmpeg_exe = None
|
|
36
38
|
if ffmpeg_path:
|
|
@@ -376,4 +378,8 @@ if __name__ == '__main__':
|
|
|
376
378
|
TEST_MODELS_SEARCH_DIR.rmdir()
|
|
377
379
|
except OSError: pass # Ignore if not empty or other issues
|
|
378
380
|
|
|
379
|
-
ASCIIColors.yellow("\n--- WhisperCppSTTBinding Test Finished ---")
|
|
381
|
+
ASCIIColors.yellow("\n--- WhisperCppSTTBinding Test Finished ---")
|
|
382
|
+
|
|
383
|
+
def list_models(self) -> List[Dict[str, Any]]:
|
|
384
|
+
return ["base" , "small", "medium", "large"]
|
|
385
|
+
|