lollms-client 0.15.2__py3-none-any.whl → 0.17.0__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.
- examples/generate_and_speak/generate_and_speak.py +251 -0
- examples/generate_game_sfx/generate_game_fx.py +240 -0
- examples/simple_text_gen_with_image_test.py +8 -8
- examples/text_2_image.py +0 -1
- examples/text_gen.py +1 -1
- lollms_client/__init__.py +1 -1
- lollms_client/llm_bindings/llamacpp/__init__.py +61 -11
- lollms_client/llm_bindings/lollms/__init__.py +31 -24
- lollms_client/llm_bindings/ollama/__init__.py +47 -27
- lollms_client/llm_bindings/openai/__init__.py +62 -35
- lollms_client/llm_bindings/openllm/__init__.py +4 -1
- lollms_client/llm_bindings/pythonllamacpp/__init__.py +3 -0
- lollms_client/llm_bindings/tensor_rt/__init__.py +4 -1
- lollms_client/llm_bindings/transformers/__init__.py +3 -0
- lollms_client/llm_bindings/vllm/__init__.py +4 -1
- lollms_client/lollms_core.py +65 -33
- lollms_client/lollms_llm_binding.py +76 -22
- lollms_client/lollms_stt_binding.py +3 -15
- lollms_client/lollms_tti_binding.py +5 -29
- lollms_client/lollms_ttm_binding.py +5 -28
- lollms_client/lollms_tts_binding.py +4 -28
- lollms_client/lollms_ttv_binding.py +4 -28
- lollms_client/lollms_utilities.py +5 -3
- lollms_client/stt_bindings/lollms/__init__.py +5 -4
- lollms_client/stt_bindings/whisper/__init__.py +304 -0
- lollms_client/stt_bindings/whispercpp/__init__.py +380 -0
- lollms_client/tti_bindings/lollms/__init__.py +4 -6
- lollms_client/ttm_bindings/audiocraft/__init__.py +281 -0
- lollms_client/ttm_bindings/bark/__init__.py +339 -0
- lollms_client/tts_bindings/bark/__init__.py +336 -0
- lollms_client/tts_bindings/piper_tts/__init__.py +343 -0
- lollms_client/tts_bindings/xtts/__init__.py +317 -0
- lollms_client-0.17.0.dist-info/METADATA +183 -0
- lollms_client-0.17.0.dist-info/RECORD +65 -0
- lollms_client-0.15.2.dist-info/METADATA +0 -192
- lollms_client-0.15.2.dist-info/RECORD +0 -56
- {lollms_client-0.15.2.dist-info → lollms_client-0.17.0.dist-info}/WHEEL +0 -0
- {lollms_client-0.15.2.dist-info → lollms_client-0.17.0.dist-info}/licenses/LICENSE +0 -0
- {lollms_client-0.15.2.dist-info → lollms_client-0.17.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
# lollms_client/stt_bindings/whispercpp/__init__.py
|
|
2
|
+
import os
|
|
3
|
+
import shutil
|
|
4
|
+
import subprocess
|
|
5
|
+
import tempfile
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional, List, Union, Dict, Any
|
|
8
|
+
|
|
9
|
+
from ascii_colors import trace_exception, ASCIIColors
|
|
10
|
+
# No pipmaster needed here as whisper.cpp is a C++ executable.
|
|
11
|
+
# Python dependencies are assumed to be handled by the environment or a higher level.
|
|
12
|
+
|
|
13
|
+
from lollms_client.lollms_stt_binding import LollmsSTTBinding
|
|
14
|
+
|
|
15
|
+
BindingName = "WhisperCppSTTBinding"
|
|
16
|
+
|
|
17
|
+
DEFAULT_WHISPERCPP_EXE_NAMES = ["main", "whisper-cli", "whisper"] # Common names for the executable
|
|
18
|
+
|
|
19
|
+
class WhisperCppSTTBinding(LollmsSTTBinding):
|
|
20
|
+
def __init__(self,
|
|
21
|
+
model_path: Union[str, Path], # Path to the GGUF Whisper model
|
|
22
|
+
whispercpp_exe_path: Optional[Union[str, Path]] = None, # Path to whisper.cpp executable
|
|
23
|
+
ffmpeg_path: Optional[Union[str, Path]] = None, # Path to ffmpeg executable (if not in PATH)
|
|
24
|
+
models_search_path: Optional[Union[str, Path]] = None, # Optional dir to scan for more models
|
|
25
|
+
default_language: str = "auto",
|
|
26
|
+
n_threads: int = 4,
|
|
27
|
+
# Catch LollmsSTTBinding standard args even if not directly used by this local binding
|
|
28
|
+
host_address: Optional[str] = None, # Not used for local binding
|
|
29
|
+
service_key: Optional[str] = None, # Not used for local binding
|
|
30
|
+
verify_ssl_certificate: bool = True, # Not used for local binding
|
|
31
|
+
**kwargs): # Catch-all for future compatibility or specific whisper.cpp params
|
|
32
|
+
|
|
33
|
+
super().__init__(binding_name="whispercpp")
|
|
34
|
+
|
|
35
|
+
# --- Validate FFMPEG ---
|
|
36
|
+
self.ffmpeg_exe = None
|
|
37
|
+
if ffmpeg_path:
|
|
38
|
+
resolved_ffmpeg_path = Path(ffmpeg_path)
|
|
39
|
+
if resolved_ffmpeg_path.is_file() and os.access(resolved_ffmpeg_path, os.X_OK):
|
|
40
|
+
self.ffmpeg_exe = str(resolved_ffmpeg_path)
|
|
41
|
+
else:
|
|
42
|
+
raise FileNotFoundError(f"Provided ffmpeg_path '{ffmpeg_path}' not found or not executable.")
|
|
43
|
+
else:
|
|
44
|
+
self.ffmpeg_exe = shutil.which("ffmpeg")
|
|
45
|
+
|
|
46
|
+
if not self.ffmpeg_exe:
|
|
47
|
+
ASCIIColors.warning("ffmpeg not found in PATH or explicitly provided. Audio conversion will not be possible for non-WAV files or incompatible WAV files.")
|
|
48
|
+
ASCIIColors.warning("Please install ffmpeg and ensure it's in your system's PATH, or provide ffmpeg_path argument.")
|
|
49
|
+
# Not raising an error here, as user might provide perfectly formatted WAV files.
|
|
50
|
+
|
|
51
|
+
# --- Validate Whisper.cpp Executable ---
|
|
52
|
+
self.whispercpp_exe = None
|
|
53
|
+
if whispercpp_exe_path:
|
|
54
|
+
resolved_wcpp_path = Path(whispercpp_exe_path)
|
|
55
|
+
if resolved_wcpp_path.is_file() and os.access(resolved_wcpp_path, os.X_OK):
|
|
56
|
+
self.whispercpp_exe = str(resolved_wcpp_path)
|
|
57
|
+
else:
|
|
58
|
+
raise FileNotFoundError(f"Provided whispercpp_exe_path '{whispercpp_exe_path}' not found or not executable.")
|
|
59
|
+
else:
|
|
60
|
+
for name in DEFAULT_WHISPERCPP_EXE_NAMES:
|
|
61
|
+
found_path = shutil.which(name)
|
|
62
|
+
if found_path:
|
|
63
|
+
self.whispercpp_exe = found_path
|
|
64
|
+
ASCIIColors.info(f"Found whisper.cpp executable via PATH: {self.whispercpp_exe}")
|
|
65
|
+
break
|
|
66
|
+
|
|
67
|
+
if not self.whispercpp_exe:
|
|
68
|
+
raise FileNotFoundError(
|
|
69
|
+
f"Whisper.cpp executable (tried: {', '.join(DEFAULT_WHISPERCPP_EXE_NAMES)}) not found in PATH or explicitly provided. "
|
|
70
|
+
"Please build/install whisper.cpp (from https://github.com/ggerganov/whisper.cpp) "
|
|
71
|
+
"and ensure its main executable is in your system's PATH or provide its path via whispercpp_exe_path argument."
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# --- Validate Model Path ---
|
|
75
|
+
self.model_path = Path(model_path)
|
|
76
|
+
if not self.model_path.is_file():
|
|
77
|
+
# Try to resolve relative to models_search_path if provided and model_path is not absolute
|
|
78
|
+
if models_search_path and not self.model_path.is_absolute() and Path(models_search_path, self.model_path).is_file():
|
|
79
|
+
self.model_path = Path(models_search_path, self.model_path).resolve()
|
|
80
|
+
else:
|
|
81
|
+
raise FileNotFoundError(f"Whisper GGUF model file not found at '{self.model_path}'. Also checked in models_search_path if applicable.")
|
|
82
|
+
|
|
83
|
+
self.models_search_path = Path(models_search_path).resolve() if models_search_path else None
|
|
84
|
+
self.default_language = default_language
|
|
85
|
+
self.n_threads = n_threads
|
|
86
|
+
self.extra_whisper_args = kwargs.get("extra_whisper_args", []) # e.g. ["--no-timestamps"]
|
|
87
|
+
|
|
88
|
+
ASCIIColors.green(f"WhisperCppSTTBinding initialized with model: {self.model_path}")
|
|
89
|
+
|
|
90
|
+
def _convert_to_wav(self, input_audio_path: Path, output_wav_path: Path) -> bool:
|
|
91
|
+
if not self.ffmpeg_exe:
|
|
92
|
+
ASCIIColors.error("ffmpeg is required for audio conversion but was not found or configured.")
|
|
93
|
+
return False
|
|
94
|
+
try:
|
|
95
|
+
command = [
|
|
96
|
+
self.ffmpeg_exe,
|
|
97
|
+
"-i", str(input_audio_path),
|
|
98
|
+
"-ar", "16000", # 16kHz sample rate
|
|
99
|
+
"-ac", "1", # Mono channel
|
|
100
|
+
"-c:a", "pcm_s16le", # Signed 16-bit PCM little-endian
|
|
101
|
+
"-y", # Overwrite output file if it exists
|
|
102
|
+
str(output_wav_path)
|
|
103
|
+
]
|
|
104
|
+
ASCIIColors.info(f"Converting audio with ffmpeg: {' '.join(command)}")
|
|
105
|
+
# Using Popen to better handle stderr/stdout if needed for detailed logging
|
|
106
|
+
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
|
107
|
+
stdout, stderr = process.communicate()
|
|
108
|
+
|
|
109
|
+
if process.returncode != 0:
|
|
110
|
+
ASCIIColors.error(f"ffmpeg conversion failed (exit code {process.returncode}).")
|
|
111
|
+
ASCIIColors.error(f"ffmpeg stdout:\n{stdout}")
|
|
112
|
+
ASCIIColors.error(f"ffmpeg stderr:\n{stderr}")
|
|
113
|
+
return False
|
|
114
|
+
ASCIIColors.info(f"ffmpeg conversion successful: {output_wav_path}")
|
|
115
|
+
return True
|
|
116
|
+
except FileNotFoundError: # Handle case where ffmpeg command itself is not found
|
|
117
|
+
ASCIIColors.error(f"ffmpeg command '{self.ffmpeg_exe}' not found. Ensure ffmpeg is installed and in PATH or ffmpeg_path is correct.")
|
|
118
|
+
return False
|
|
119
|
+
except Exception as e:
|
|
120
|
+
ASCIIColors.error(f"An error occurred during ffmpeg conversion: {e}")
|
|
121
|
+
trace_exception(e)
|
|
122
|
+
return False
|
|
123
|
+
|
|
124
|
+
def transcribe_audio(self, audio_path: Union[str, Path], model: Optional[str] = None, **kwargs) -> str:
|
|
125
|
+
input_audio_p = Path(audio_path)
|
|
126
|
+
if not input_audio_p.exists():
|
|
127
|
+
raise FileNotFoundError(f"Input audio file not found: {input_audio_p}")
|
|
128
|
+
|
|
129
|
+
current_model_path = self.model_path
|
|
130
|
+
if model: # User specified a different model for this transcription
|
|
131
|
+
potential_model_p = Path(model)
|
|
132
|
+
if potential_model_p.is_absolute() and potential_model_p.is_file():
|
|
133
|
+
current_model_path = potential_model_p
|
|
134
|
+
elif self.models_search_path and (self.models_search_path / model).is_file():
|
|
135
|
+
current_model_path = self.models_search_path / model
|
|
136
|
+
elif Path(model).is_file(): # Relative to current working directory?
|
|
137
|
+
current_model_path = Path(model)
|
|
138
|
+
else:
|
|
139
|
+
ASCIIColors.warning(f"Specified model '{model}' not found as absolute path, in models_search_path, or current dir. Using default: {self.model_path.name}")
|
|
140
|
+
|
|
141
|
+
language = kwargs.get("language", self.default_language)
|
|
142
|
+
threads = kwargs.get("n_threads", self.n_threads)
|
|
143
|
+
extra_args_call = kwargs.get("extra_whisper_args", self.extra_whisper_args)
|
|
144
|
+
|
|
145
|
+
with tempfile.TemporaryDirectory(prefix="lollms_whispercpp_") as tmpdir:
|
|
146
|
+
tmp_dir_path = Path(tmpdir)
|
|
147
|
+
|
|
148
|
+
# Always convert to ensure 16kHz mono WAV, unless explicitly told not to by a kwarg (e.g. assume_wav=True)
|
|
149
|
+
force_conversion = not kwargs.get("assume_compatible_wav", False)
|
|
150
|
+
|
|
151
|
+
if force_conversion or input_audio_p.suffix.lower() != ".wav":
|
|
152
|
+
if not self.ffmpeg_exe:
|
|
153
|
+
raise RuntimeError("ffmpeg is required for audio pre-processing but is not configured. "
|
|
154
|
+
"Please provide a 16kHz mono WAV file or configure ffmpeg.")
|
|
155
|
+
converted_wav_path = tmp_dir_path / (input_audio_p.stem + "_16khz_mono.wav")
|
|
156
|
+
if not self._convert_to_wav(input_audio_p, converted_wav_path):
|
|
157
|
+
raise Exception(f"Audio conversion to compatible WAV failed for {input_audio_p}.")
|
|
158
|
+
target_audio_file = converted_wav_path
|
|
159
|
+
else: # Input is WAV, assume it's compatible (user's responsibility if assume_compatible_wav=True)
|
|
160
|
+
target_audio_file = input_audio_p
|
|
161
|
+
|
|
162
|
+
command = [
|
|
163
|
+
self.whispercpp_exe,
|
|
164
|
+
"-m", str(current_model_path),
|
|
165
|
+
"-f", str(target_audio_file),
|
|
166
|
+
"-l", language,
|
|
167
|
+
"-t", str(threads),
|
|
168
|
+
"-otxt" # Output as a .txt file in the same dir as input wav
|
|
169
|
+
]
|
|
170
|
+
if isinstance(extra_args_call, list):
|
|
171
|
+
command.extend(extra_args_call)
|
|
172
|
+
elif isinstance(extra_args_call, str):
|
|
173
|
+
command.extend(extra_args_call.split())
|
|
174
|
+
|
|
175
|
+
ASCIIColors.info(f"Executing Whisper.cpp: {' '.join(command)}")
|
|
176
|
+
try:
|
|
177
|
+
# Run whisper.cpp, making it output its .txt file into our temp directory.
|
|
178
|
+
# To do this, we can copy the target_audio_file into tmp_dir_path if it's not already there,
|
|
179
|
+
# then run whisper.cpp with CWD as tmp_dir_path.
|
|
180
|
+
|
|
181
|
+
final_target_audio_in_tmp: Path
|
|
182
|
+
if target_audio_file.parent != tmp_dir_path:
|
|
183
|
+
final_target_audio_in_tmp = tmp_dir_path / target_audio_file.name
|
|
184
|
+
shutil.copy2(target_audio_file, final_target_audio_in_tmp)
|
|
185
|
+
# Update command to use the path within tmp_dir_path if we copied it.
|
|
186
|
+
# The -f argument should be relative to the CWD if CWD is set.
|
|
187
|
+
command[command.index("-f")+1] = str(final_target_audio_in_tmp.name)
|
|
188
|
+
else:
|
|
189
|
+
final_target_audio_in_tmp = target_audio_file
|
|
190
|
+
command[command.index("-f")+1] = str(final_target_audio_in_tmp.name)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
process = subprocess.run(command, capture_output=True, text=True, check=True, cwd=str(tmp_dir_path))
|
|
194
|
+
|
|
195
|
+
output_txt_file = tmp_dir_path / (final_target_audio_in_tmp.name + ".txt")
|
|
196
|
+
|
|
197
|
+
if output_txt_file.exists():
|
|
198
|
+
transcribed_text = output_txt_file.read_text(encoding='utf-8').strip()
|
|
199
|
+
ASCIIColors.green(f"Whisper.cpp transcription successful for {input_audio_p.name}.")
|
|
200
|
+
return transcribed_text
|
|
201
|
+
else:
|
|
202
|
+
ASCIIColors.error(f"Whisper.cpp did not produce the expected output file: {output_txt_file.name} in {tmp_dir_path}")
|
|
203
|
+
ASCIIColors.info(f"Whisper.cpp stdout:\n{process.stdout}")
|
|
204
|
+
ASCIIColors.info(f"Whisper.cpp stderr:\n{process.stderr}")
|
|
205
|
+
raise Exception("Whisper.cpp execution failed to produce output text file.")
|
|
206
|
+
|
|
207
|
+
except subprocess.CalledProcessError as e:
|
|
208
|
+
ASCIIColors.error(f"Whisper.cpp execution failed with exit code {e.returncode} for {input_audio_p.name}")
|
|
209
|
+
ASCIIColors.error(f"Command: {' '.join(e.cmd)}")
|
|
210
|
+
ASCIIColors.error(f"Stdout:\n{e.stdout}")
|
|
211
|
+
ASCIIColors.error(f"Stderr:\n{e.stderr}")
|
|
212
|
+
trace_exception(e)
|
|
213
|
+
raise Exception(f"Whisper.cpp execution error: {e.stderr or e.stdout or 'Unknown whisper.cpp error'}") from e
|
|
214
|
+
except Exception as e:
|
|
215
|
+
ASCIIColors.error(f"An error occurred during Whisper.cpp transcription for {input_audio_p.name}: {e}")
|
|
216
|
+
trace_exception(e)
|
|
217
|
+
raise
|
|
218
|
+
|
|
219
|
+
def list_models(self, **kwargs) -> List[str]:
|
|
220
|
+
models = []
|
|
221
|
+
# 1. Add the default configured model's name
|
|
222
|
+
if self.model_path and self.model_path.exists():
|
|
223
|
+
# For consistency, list by name. The 'model' arg in transcribe_audio can take this name.
|
|
224
|
+
models.append(self.model_path.name)
|
|
225
|
+
|
|
226
|
+
# 2. Scan models_search_path if provided
|
|
227
|
+
if self.models_search_path and self.models_search_path.is_dir():
|
|
228
|
+
for item in self.models_search_path.iterdir():
|
|
229
|
+
if item.is_file() and item.suffix.lower() == ".gguf":
|
|
230
|
+
# Add name if not already listed (default model might be in search path)
|
|
231
|
+
if item.name not in models:
|
|
232
|
+
models.append(item.name)
|
|
233
|
+
|
|
234
|
+
return sorted(list(set(models))) # Ensure uniqueness and sort
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
# --- Main Test Block ---
|
|
238
|
+
if __name__ == '__main__':
|
|
239
|
+
ASCIIColors.yellow("--- WhisperCppSTTBinding Test ---")
|
|
240
|
+
|
|
241
|
+
# --- USER CONFIGURATION REQUIRED FOR TEST ---
|
|
242
|
+
# Find your whisper.cpp build directory and the 'main' or 'whisper-cli' executable.
|
|
243
|
+
# Example: If you built whisper.cpp in /home/user/whisper.cpp, the exe might be /home/user/whisper.cpp/main
|
|
244
|
+
TEST_WHISPERCPP_EXE = None # SET THIS: e.g., "/path/to/whisper.cpp/main" or "whisper-cli" if in PATH
|
|
245
|
+
|
|
246
|
+
# Download a GGUF model from Hugging Face: https://huggingface.co/ggerganov/whisper.cpp/tree/main
|
|
247
|
+
# Place it somewhere accessible.
|
|
248
|
+
TEST_MODEL_GGUF = "ggml-tiny.en.bin" # SET THIS: e.g., "/path/to/models/ggml-tiny.en.bin"
|
|
249
|
+
# If just a name, expects it in CWD or models_search_path.
|
|
250
|
+
|
|
251
|
+
# Optional: Path to ffmpeg if not in system PATH
|
|
252
|
+
TEST_FFMPEG_EXE = None # e.g., "/usr/local/bin/ffmpeg"
|
|
253
|
+
|
|
254
|
+
# Optional: A directory to put other .gguf models for testing list_models
|
|
255
|
+
TEST_MODELS_SEARCH_DIR = Path("./test_whisper_models_cpp")
|
|
256
|
+
# --- END USER CONFIGURATION ---
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
# Create a dummy audio file for testing (requires scipy and numpy)
|
|
260
|
+
dummy_audio_file = Path("dummy_whispercpp_test.wav")
|
|
261
|
+
if not dummy_audio_file.exists():
|
|
262
|
+
try:
|
|
263
|
+
import numpy as np
|
|
264
|
+
from scipy.io.wavfile import write as write_wav
|
|
265
|
+
samplerate = 44100; duration = 1.5; frequency = 330 # A bit longer, different freq
|
|
266
|
+
t = np.linspace(0., duration, int(samplerate * duration), endpoint=False)
|
|
267
|
+
amplitude = np.iinfo(np.int16).max * 0.3
|
|
268
|
+
data = amplitude * np.sin(2. * np.pi * frequency * t)
|
|
269
|
+
# Add some noise
|
|
270
|
+
data += (amplitude * 0.05 * np.random.normal(size=data.shape[0])).astype(data.dtype)
|
|
271
|
+
write_wav(dummy_audio_file, samplerate, data.astype(np.int16))
|
|
272
|
+
ASCIIColors.green(f"Created dummy audio file for testing: {dummy_audio_file}")
|
|
273
|
+
except ImportError:
|
|
274
|
+
ASCIIColors.warning("SciPy/NumPy not installed. Cannot create dummy audio file for testing.")
|
|
275
|
+
ASCIIColors.warning(f"Please manually place a test audio file named '{dummy_audio_file.name}' in the current directory.")
|
|
276
|
+
except Exception as e_dummy:
|
|
277
|
+
ASCIIColors.error(f"Could not create dummy audio file: {e_dummy}")
|
|
278
|
+
|
|
279
|
+
# Prepare model search directory for list_models test
|
|
280
|
+
if TEST_MODELS_SEARCH_DIR:
|
|
281
|
+
TEST_MODELS_SEARCH_DIR.mkdir(exist_ok=True)
|
|
282
|
+
# Create another dummy GGUF (just an empty file for listing purposes)
|
|
283
|
+
(TEST_MODELS_SEARCH_DIR / "ggml-base.en.bin").touch(exist_ok=True)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
# Basic check for prerequisites before attempting to initialize
|
|
287
|
+
if TEST_WHISPERCPP_EXE is None and not any(shutil.which(name) for name in DEFAULT_WHISPERCPP_EXE_NAMES):
|
|
288
|
+
ASCIIColors.error(f"Whisper.cpp executable not found in PATH and TEST_WHISPERCPP_EXE not set. Aborting test.")
|
|
289
|
+
exit(1)
|
|
290
|
+
|
|
291
|
+
if not Path(TEST_MODEL_GGUF).exists() and not (TEST_MODELS_SEARCH_DIR and (TEST_MODELS_SEARCH_DIR / TEST_MODEL_GGUF).exists()):
|
|
292
|
+
# Check if model is just a name and exists in current dir if TEST_MODELS_SEARCH_DIR is not set or model not in it
|
|
293
|
+
if not (Path().cwd()/TEST_MODEL_GGUF).exists():
|
|
294
|
+
ASCIIColors.error(f"Test model GGUF '{TEST_MODEL_GGUF}' not found. Please download/place it or update TEST_MODEL_GGUF path. Aborting test.")
|
|
295
|
+
exit(1)
|
|
296
|
+
else: # Found in CWD
|
|
297
|
+
TEST_MODEL_GGUF = str((Path().cwd()/TEST_MODEL_GGUF).resolve())
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
stt_binding = None
|
|
301
|
+
try:
|
|
302
|
+
ASCIIColors.cyan("\n--- Initializing WhisperCppSTTBinding ---")
|
|
303
|
+
stt_binding = WhisperCppSTTBinding(
|
|
304
|
+
model_path=TEST_MODEL_GGUF,
|
|
305
|
+
whispercpp_exe_path=TEST_WHISPERCPP_EXE,
|
|
306
|
+
ffmpeg_path=TEST_FFMPEG_EXE,
|
|
307
|
+
models_search_path=TEST_MODELS_SEARCH_DIR,
|
|
308
|
+
default_language="en",
|
|
309
|
+
n_threads=os.cpu_count() // 2 or 1, # Use half CPU cores or at least 1
|
|
310
|
+
)
|
|
311
|
+
ASCIIColors.green("Binding initialized successfully.")
|
|
312
|
+
|
|
313
|
+
ASCIIColors.cyan("\n--- Listing available models ---")
|
|
314
|
+
models = stt_binding.list_models()
|
|
315
|
+
if models:
|
|
316
|
+
print(f"Available models: {models}")
|
|
317
|
+
else:
|
|
318
|
+
ASCIIColors.warning("No models listed. Check paths and models_search_path.")
|
|
319
|
+
|
|
320
|
+
if dummy_audio_file.exists():
|
|
321
|
+
ASCIIColors.cyan(f"\n--- Transcribing '{dummy_audio_file.name}' (expected 16kHz mono after conversion) ---")
|
|
322
|
+
transcription = stt_binding.transcribe_audio(str(dummy_audio_file))
|
|
323
|
+
print(f"Transcription: '{transcription}'")
|
|
324
|
+
|
|
325
|
+
# Test with a different model if listed and available
|
|
326
|
+
if "ggml-base.en.bin" in models and "ggml-base.en.bin" != Path(TEST_MODEL_GGUF).name :
|
|
327
|
+
if (TEST_MODELS_SEARCH_DIR / "ggml-base.en.bin").exists(): # Ensure it's actually there
|
|
328
|
+
ASCIIColors.cyan(f"\n--- Transcribing with model 'ggml-base.en.bin' from search path ---")
|
|
329
|
+
transcription_base = stt_binding.transcribe_audio(str(dummy_audio_file), model="ggml-base.en.bin")
|
|
330
|
+
print(f"Transcription (ggml-base.en.bin): '{transcription_base}'")
|
|
331
|
+
else:
|
|
332
|
+
ASCIIColors.warning("Model 'ggml-base.en.bin' listed but not found in search path for test.")
|
|
333
|
+
|
|
334
|
+
# Test assume_compatible_wav (if user has a 16kHz mono WAV already)
|
|
335
|
+
# Create a specific 16kHz mono wav file for this
|
|
336
|
+
compatible_wav_file = Path("compatible_test.wav")
|
|
337
|
+
try:
|
|
338
|
+
import numpy as np
|
|
339
|
+
from scipy.io.wavfile import write as write_wav
|
|
340
|
+
samplerate = 16000; duration = 1.0; frequency = 550
|
|
341
|
+
t_compat = np.linspace(0., duration, int(samplerate * duration), endpoint=False)
|
|
342
|
+
data_compat = (np.iinfo(np.int16).max * 0.2 * np.sin(2. * np.pi * frequency * t_compat)).astype(np.int16)
|
|
343
|
+
write_wav(compatible_wav_file, samplerate, data_compat)
|
|
344
|
+
ASCIIColors.green(f"Created compatible 16kHz mono WAV: {compatible_wav_file}")
|
|
345
|
+
|
|
346
|
+
ASCIIColors.cyan(f"\n--- Transcribing '{compatible_wav_file.name}' with assume_compatible_wav=True ---")
|
|
347
|
+
transcription_compat = stt_binding.transcribe_audio(str(compatible_wav_file), assume_compatible_wav=True)
|
|
348
|
+
print(f"Transcription (compatible WAV): '{transcription_compat}'")
|
|
349
|
+
|
|
350
|
+
except ImportError: ASCIIColors.warning("SciPy/NumPy not available, skipping compatible WAV test.")
|
|
351
|
+
except Exception as e_compat: ASCIIColors.error(f"Error in compatible WAV test: {e_compat}")
|
|
352
|
+
finally:
|
|
353
|
+
if compatible_wav_file.exists(): compatible_wav_file.unlink(missing_ok=True)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
else:
|
|
357
|
+
ASCIIColors.warning(f"Dummy audio file '{dummy_audio_file}' not found. Skipping main transcription test.")
|
|
358
|
+
|
|
359
|
+
except FileNotFoundError as e:
|
|
360
|
+
ASCIIColors.error(f"Initialization or transcription failed due to FileNotFoundError: {e}")
|
|
361
|
+
ASCIIColors.info("Please ensure whisper.cpp/ffmpeg executables are in PATH or paths are correctly set in the test script, and the GGUF model file exists.")
|
|
362
|
+
except RuntimeError as e:
|
|
363
|
+
ASCIIColors.error(f"Runtime error: {e}")
|
|
364
|
+
except Exception as e:
|
|
365
|
+
ASCIIColors.error(f"An unexpected error occurred: {e}")
|
|
366
|
+
trace_exception(e)
|
|
367
|
+
finally:
|
|
368
|
+
# Clean up dummy files created by this test script
|
|
369
|
+
if "samplerate" in locals() and dummy_audio_file.exists(): # Heuristic: if we created it
|
|
370
|
+
dummy_audio_file.unlink(missing_ok=True)
|
|
371
|
+
if TEST_MODELS_SEARCH_DIR:
|
|
372
|
+
if (TEST_MODELS_SEARCH_DIR / "ggml-base.en.bin").exists():
|
|
373
|
+
(TEST_MODELS_SEARCH_DIR / "ggml-base.en.bin").unlink(missing_ok=True)
|
|
374
|
+
# Remove dir only if it's empty (or was created by this script and now empty)
|
|
375
|
+
try:
|
|
376
|
+
if not any(TEST_MODELS_SEARCH_DIR.iterdir()):
|
|
377
|
+
TEST_MODELS_SEARCH_DIR.rmdir()
|
|
378
|
+
except OSError: pass # Ignore if not empty or other issues
|
|
379
|
+
|
|
380
|
+
ASCIIColors.yellow("\n--- WhisperCppSTTBinding Test Finished ---")
|
|
@@ -16,7 +16,6 @@ class LollmsTTIBinding_Impl(LollmsTTIBinding):
|
|
|
16
16
|
|
|
17
17
|
def __init__(self,
|
|
18
18
|
host_address: Optional[str] = "http://localhost:9600", # Default LOLLMS host
|
|
19
|
-
model_name: Optional[str] = None, # Default service name (server decides if None)
|
|
20
19
|
service_key: Optional[str] = None,
|
|
21
20
|
verify_ssl_certificate: bool = True):
|
|
22
21
|
"""
|
|
@@ -24,14 +23,13 @@ class LollmsTTIBinding_Impl(LollmsTTIBinding):
|
|
|
24
23
|
|
|
25
24
|
Args:
|
|
26
25
|
host_address (Optional[str]): Host address for the LOLLMS service.
|
|
27
|
-
model_name (Optional[str]): Default service/model identifier (currently unused by LOLLMS TTI endpoints).
|
|
28
26
|
service_key (Optional[str]): Authentication key (used for client_id verification).
|
|
29
27
|
verify_ssl_certificate (bool): Whether to verify SSL certificates.
|
|
30
28
|
"""
|
|
31
|
-
super().__init__(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
super().__init__(binding_name="lollms")
|
|
30
|
+
self.host_address=host_address
|
|
31
|
+
self.verify_ssl_certificate = verify_ssl_certificate
|
|
32
|
+
|
|
35
33
|
# The 'service_key' here will act as the 'client_id' for TTI requests if provided.
|
|
36
34
|
# This assumes the client library user provides their LOLLMS client_id here.
|
|
37
35
|
self.client_id = service_key
|