abstractvoice 0.5.2__py3-none-any.whl → 0.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.
- abstractvoice/__init__.py +2 -5
- abstractvoice/__main__.py +82 -3
- abstractvoice/adapters/__init__.py +12 -0
- abstractvoice/adapters/base.py +207 -0
- abstractvoice/adapters/stt_faster_whisper.py +401 -0
- abstractvoice/adapters/tts_piper.py +480 -0
- abstractvoice/aec/__init__.py +10 -0
- abstractvoice/aec/webrtc_apm.py +56 -0
- abstractvoice/artifacts.py +173 -0
- abstractvoice/audio/__init__.py +7 -0
- abstractvoice/audio/recorder.py +46 -0
- abstractvoice/audio/resample.py +25 -0
- abstractvoice/cloning/__init__.py +7 -0
- abstractvoice/cloning/engine_chroma.py +738 -0
- abstractvoice/cloning/engine_f5.py +546 -0
- abstractvoice/cloning/manager.py +349 -0
- abstractvoice/cloning/store.py +362 -0
- abstractvoice/compute/__init__.py +6 -0
- abstractvoice/compute/device.py +73 -0
- abstractvoice/config/__init__.py +2 -0
- abstractvoice/config/voice_catalog.py +19 -0
- abstractvoice/dependency_check.py +0 -1
- abstractvoice/examples/cli_repl.py +2408 -243
- abstractvoice/examples/voice_cli.py +64 -63
- abstractvoice/integrations/__init__.py +2 -0
- abstractvoice/integrations/abstractcore.py +116 -0
- abstractvoice/integrations/abstractcore_plugin.py +253 -0
- abstractvoice/prefetch.py +82 -0
- abstractvoice/recognition.py +424 -42
- abstractvoice/stop_phrase.py +103 -0
- abstractvoice/text_sanitize.py +33 -0
- abstractvoice/tts/__init__.py +3 -3
- abstractvoice/tts/adapter_tts_engine.py +210 -0
- abstractvoice/tts/tts_engine.py +257 -1208
- abstractvoice/vm/__init__.py +2 -0
- abstractvoice/vm/common.py +21 -0
- abstractvoice/vm/core.py +139 -0
- abstractvoice/vm/manager.py +108 -0
- abstractvoice/vm/stt_mixin.py +158 -0
- abstractvoice/vm/tts_mixin.py +550 -0
- abstractvoice/voice_manager.py +6 -1061
- abstractvoice-0.6.2.dist-info/METADATA +213 -0
- abstractvoice-0.6.2.dist-info/RECORD +53 -0
- {abstractvoice-0.5.2.dist-info → abstractvoice-0.6.2.dist-info}/WHEEL +1 -1
- abstractvoice-0.6.2.dist-info/entry_points.txt +6 -0
- abstractvoice/instant_setup.py +0 -83
- abstractvoice/simple_model_manager.py +0 -539
- abstractvoice-0.5.2.dist-info/METADATA +0 -1458
- abstractvoice-0.5.2.dist-info/RECORD +0 -23
- abstractvoice-0.5.2.dist-info/entry_points.txt +0 -2
- {abstractvoice-0.5.2.dist-info → abstractvoice-0.6.2.dist-info}/licenses/LICENSE +0 -0
- {abstractvoice-0.5.2.dist-info → abstractvoice-0.6.2.dist-info}/top_level.txt +0 -0
|
@@ -1,539 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Simple Model Manager for AbstractVoice
|
|
3
|
-
|
|
4
|
-
Provides clean, simple APIs for model management that can be used by both
|
|
5
|
-
CLI commands and third-party applications.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import os
|
|
9
|
-
import json
|
|
10
|
-
import time
|
|
11
|
-
import threading
|
|
12
|
-
from typing import Dict, List, Optional, Callable, Any
|
|
13
|
-
from pathlib import Path
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def _import_tts():
|
|
17
|
-
"""Import TTS with helpful error message if dependencies missing."""
|
|
18
|
-
try:
|
|
19
|
-
from TTS.api import TTS
|
|
20
|
-
from TTS.utils.manage import ModelManager
|
|
21
|
-
return TTS, ModelManager
|
|
22
|
-
except ImportError as e:
|
|
23
|
-
raise ImportError(
|
|
24
|
-
"TTS functionality requires coqui-tts. Install with:\n"
|
|
25
|
-
" pip install abstractvoice[tts]\n"
|
|
26
|
-
f"Original error: {e}"
|
|
27
|
-
) from e
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class SimpleModelManager:
|
|
31
|
-
"""Simple, clean model manager for AbstractVoice."""
|
|
32
|
-
|
|
33
|
-
# Essential model - guaranteed to work everywhere, reasonable size
|
|
34
|
-
# Changed from fast_pitch to tacotron2-DDC because fast_pitch downloads are failing
|
|
35
|
-
ESSENTIAL_MODEL = "tts_models/en/ljspeech/tacotron2-DDC"
|
|
36
|
-
|
|
37
|
-
# Available models organized by language with metadata
|
|
38
|
-
AVAILABLE_MODELS = {
|
|
39
|
-
"en": {
|
|
40
|
-
"tacotron2": {
|
|
41
|
-
"model": "tts_models/en/ljspeech/tacotron2-DDC",
|
|
42
|
-
"name": "Linda (LJSpeech)",
|
|
43
|
-
"quality": "good",
|
|
44
|
-
"size_mb": 362,
|
|
45
|
-
"description": "Standard female voice (LJSpeech speaker)",
|
|
46
|
-
"requires_espeak": False,
|
|
47
|
-
"default": True
|
|
48
|
-
},
|
|
49
|
-
"jenny": {
|
|
50
|
-
"model": "tts_models/en/jenny/jenny",
|
|
51
|
-
"name": "Jenny",
|
|
52
|
-
"quality": "excellent",
|
|
53
|
-
"size_mb": 368,
|
|
54
|
-
"description": "Different female voice, clear and natural",
|
|
55
|
-
"requires_espeak": False,
|
|
56
|
-
"default": False
|
|
57
|
-
},
|
|
58
|
-
"ek1": {
|
|
59
|
-
"model": "tts_models/en/ek1/tacotron2",
|
|
60
|
-
"name": "Edward (EK1)",
|
|
61
|
-
"quality": "excellent",
|
|
62
|
-
"size_mb": 310,
|
|
63
|
-
"description": "Male voice with British accent",
|
|
64
|
-
"requires_espeak": False,
|
|
65
|
-
"default": False
|
|
66
|
-
},
|
|
67
|
-
"sam": {
|
|
68
|
-
"model": "tts_models/en/sam/tacotron-DDC",
|
|
69
|
-
"name": "Sam",
|
|
70
|
-
"quality": "good",
|
|
71
|
-
"size_mb": 370,
|
|
72
|
-
"description": "Different male voice, deeper tone",
|
|
73
|
-
"requires_espeak": False,
|
|
74
|
-
"default": False
|
|
75
|
-
},
|
|
76
|
-
"fast_pitch": {
|
|
77
|
-
"model": "tts_models/en/ljspeech/fast_pitch",
|
|
78
|
-
"name": "Linda Fast (LJSpeech)",
|
|
79
|
-
"quality": "good",
|
|
80
|
-
"size_mb": 107,
|
|
81
|
-
"description": "Same speaker as Linda but faster engine",
|
|
82
|
-
"requires_espeak": False,
|
|
83
|
-
"default": False
|
|
84
|
-
},
|
|
85
|
-
"vits": {
|
|
86
|
-
"model": "tts_models/en/ljspeech/vits",
|
|
87
|
-
"name": "Linda Premium (LJSpeech)",
|
|
88
|
-
"quality": "excellent",
|
|
89
|
-
"size_mb": 328,
|
|
90
|
-
"description": "Same speaker as Linda but premium quality",
|
|
91
|
-
"requires_espeak": True,
|
|
92
|
-
"default": False
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
"fr": {
|
|
96
|
-
"css10_vits": {
|
|
97
|
-
"model": "tts_models/fr/css10/vits",
|
|
98
|
-
"name": "CSS10 VITS (French)",
|
|
99
|
-
"quality": "excellent",
|
|
100
|
-
"size_mb": 548,
|
|
101
|
-
"description": "High-quality French voice",
|
|
102
|
-
"requires_espeak": True,
|
|
103
|
-
"default": True
|
|
104
|
-
},
|
|
105
|
-
"mai_tacotron2": {
|
|
106
|
-
"model": "tts_models/fr/mai/tacotron2-DDC",
|
|
107
|
-
"name": "MAI Tacotron2 (French)",
|
|
108
|
-
"quality": "good",
|
|
109
|
-
"size_mb": 362,
|
|
110
|
-
"description": "Reliable French voice",
|
|
111
|
-
"requires_espeak": False,
|
|
112
|
-
"default": False
|
|
113
|
-
}
|
|
114
|
-
},
|
|
115
|
-
"es": {
|
|
116
|
-
"mai_tacotron2": {
|
|
117
|
-
"model": "tts_models/es/mai/tacotron2-DDC",
|
|
118
|
-
"name": "MAI Tacotron2 (Spanish)",
|
|
119
|
-
"quality": "good",
|
|
120
|
-
"size_mb": 362,
|
|
121
|
-
"description": "Reliable Spanish voice",
|
|
122
|
-
"requires_espeak": False,
|
|
123
|
-
"default": True
|
|
124
|
-
},
|
|
125
|
-
"css10_vits": {
|
|
126
|
-
"model": "tts_models/es/css10/vits",
|
|
127
|
-
"name": "CSS10 VITS (Spanish)",
|
|
128
|
-
"quality": "excellent",
|
|
129
|
-
"size_mb": 548,
|
|
130
|
-
"description": "High-quality Spanish voice",
|
|
131
|
-
"requires_espeak": True,
|
|
132
|
-
"default": False
|
|
133
|
-
}
|
|
134
|
-
},
|
|
135
|
-
"de": {
|
|
136
|
-
"thorsten_vits": {
|
|
137
|
-
"model": "tts_models/de/thorsten/vits",
|
|
138
|
-
"name": "Thorsten VITS (German)",
|
|
139
|
-
"quality": "excellent",
|
|
140
|
-
"size_mb": 548,
|
|
141
|
-
"description": "High-quality German voice",
|
|
142
|
-
"requires_espeak": True,
|
|
143
|
-
"default": True
|
|
144
|
-
}
|
|
145
|
-
},
|
|
146
|
-
"it": {
|
|
147
|
-
"mai_male_vits": {
|
|
148
|
-
"model": "tts_models/it/mai_male/vits",
|
|
149
|
-
"name": "MAI Male VITS (Italian)",
|
|
150
|
-
"quality": "excellent",
|
|
151
|
-
"size_mb": 548,
|
|
152
|
-
"description": "High-quality Italian male voice",
|
|
153
|
-
"requires_espeak": True,
|
|
154
|
-
"default": True
|
|
155
|
-
},
|
|
156
|
-
"mai_female_vits": {
|
|
157
|
-
"model": "tts_models/it/mai_female/vits",
|
|
158
|
-
"name": "MAI Female VITS (Italian)",
|
|
159
|
-
"quality": "excellent",
|
|
160
|
-
"size_mb": 548,
|
|
161
|
-
"description": "High-quality Italian female voice",
|
|
162
|
-
"requires_espeak": True,
|
|
163
|
-
"default": False
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
def __init__(self, debug_mode: bool = False):
|
|
169
|
-
self.debug_mode = debug_mode
|
|
170
|
-
self._cache_dir = None
|
|
171
|
-
|
|
172
|
-
@property
|
|
173
|
-
def cache_dir(self) -> str:
|
|
174
|
-
"""Get the TTS model cache directory."""
|
|
175
|
-
if self._cache_dir is None:
|
|
176
|
-
# Check common cache locations
|
|
177
|
-
import appdirs
|
|
178
|
-
potential_dirs = [
|
|
179
|
-
os.path.expanduser("~/.cache/tts"),
|
|
180
|
-
appdirs.user_data_dir("tts"),
|
|
181
|
-
os.path.expanduser("~/.local/share/tts"),
|
|
182
|
-
os.path.expanduser("~/Library/Application Support/tts"), # macOS
|
|
183
|
-
]
|
|
184
|
-
|
|
185
|
-
# Find existing cache or use default
|
|
186
|
-
for cache_dir in potential_dirs:
|
|
187
|
-
if os.path.exists(cache_dir):
|
|
188
|
-
self._cache_dir = cache_dir
|
|
189
|
-
break
|
|
190
|
-
else:
|
|
191
|
-
# Use appdirs default
|
|
192
|
-
self._cache_dir = appdirs.user_data_dir("tts")
|
|
193
|
-
|
|
194
|
-
return self._cache_dir
|
|
195
|
-
|
|
196
|
-
def is_model_cached(self, model_name: str) -> bool:
|
|
197
|
-
"""Check if a specific model is cached locally."""
|
|
198
|
-
try:
|
|
199
|
-
# Convert model name to cache directory structure
|
|
200
|
-
cache_name = model_name.replace("/", "--")
|
|
201
|
-
model_path = os.path.join(self.cache_dir, cache_name)
|
|
202
|
-
|
|
203
|
-
if not os.path.exists(model_path):
|
|
204
|
-
return False
|
|
205
|
-
|
|
206
|
-
# Check for essential model files
|
|
207
|
-
essential_files = ["model.pth", "config.json"]
|
|
208
|
-
return any(os.path.exists(os.path.join(model_path, f)) for f in essential_files)
|
|
209
|
-
except Exception as e:
|
|
210
|
-
if self.debug_mode:
|
|
211
|
-
print(f"Error checking cache for {model_name}: {e}")
|
|
212
|
-
return False
|
|
213
|
-
|
|
214
|
-
def download_model(self, model_name: str, progress_callback: Optional[Callable[[str, bool], None]] = None) -> bool:
|
|
215
|
-
"""Download a specific model with improved error handling.
|
|
216
|
-
|
|
217
|
-
Args:
|
|
218
|
-
model_name: TTS model name (e.g., 'tts_models/en/ljspeech/fast_pitch')
|
|
219
|
-
progress_callback: Optional callback function(model_name, success)
|
|
220
|
-
|
|
221
|
-
Returns:
|
|
222
|
-
bool: True if successful
|
|
223
|
-
"""
|
|
224
|
-
if self.is_model_cached(model_name):
|
|
225
|
-
if self.debug_mode:
|
|
226
|
-
print(f"✅ {model_name} already cached")
|
|
227
|
-
if progress_callback:
|
|
228
|
-
progress_callback(model_name, True)
|
|
229
|
-
return True
|
|
230
|
-
|
|
231
|
-
try:
|
|
232
|
-
TTS, _ = _import_tts()
|
|
233
|
-
|
|
234
|
-
print(f"📥 Downloading {model_name}...")
|
|
235
|
-
print(f" This may take a few minutes depending on your connection...")
|
|
236
|
-
|
|
237
|
-
start_time = time.time()
|
|
238
|
-
|
|
239
|
-
# Initialize TTS to trigger download
|
|
240
|
-
# Set gpu=False to avoid CUDA errors on systems without GPU
|
|
241
|
-
try:
|
|
242
|
-
tts = TTS(model_name=model_name, progress_bar=True, gpu=False)
|
|
243
|
-
|
|
244
|
-
# Verify the model actually downloaded
|
|
245
|
-
if not self.is_model_cached(model_name):
|
|
246
|
-
print(f"⚠️ Model download completed but not found in cache")
|
|
247
|
-
return False
|
|
248
|
-
|
|
249
|
-
except Exception as init_error:
|
|
250
|
-
# Try alternative download method
|
|
251
|
-
error_msg = str(init_error).lower()
|
|
252
|
-
if "connection" in error_msg or "timeout" in error_msg:
|
|
253
|
-
print(f"❌ Network error: Check your internet connection")
|
|
254
|
-
elif "not found" in error_msg:
|
|
255
|
-
print(f"❌ Model '{model_name}' not found in registry")
|
|
256
|
-
else:
|
|
257
|
-
print(f"❌ Download error: {init_error}")
|
|
258
|
-
raise
|
|
259
|
-
|
|
260
|
-
download_time = time.time() - start_time
|
|
261
|
-
print(f"✅ Downloaded {model_name} in {download_time:.1f}s")
|
|
262
|
-
|
|
263
|
-
if progress_callback:
|
|
264
|
-
progress_callback(model_name, True)
|
|
265
|
-
return True
|
|
266
|
-
|
|
267
|
-
except Exception as e:
|
|
268
|
-
error_msg = str(e).lower()
|
|
269
|
-
|
|
270
|
-
# Provide helpful error messages
|
|
271
|
-
if "connection" in error_msg or "timeout" in error_msg:
|
|
272
|
-
print(f"❌ Failed to download {model_name}: Network issue")
|
|
273
|
-
print(f" Check your internet connection and try again")
|
|
274
|
-
elif "permission" in error_msg:
|
|
275
|
-
print(f"❌ Failed to download {model_name}: Permission denied")
|
|
276
|
-
print(f" Check write permissions for cache directory")
|
|
277
|
-
elif "space" in error_msg:
|
|
278
|
-
print(f"❌ Failed to download {model_name}: Insufficient disk space")
|
|
279
|
-
else:
|
|
280
|
-
print(f"❌ Failed to download {model_name}")
|
|
281
|
-
if self.debug_mode:
|
|
282
|
-
print(f" Error: {e}")
|
|
283
|
-
|
|
284
|
-
if progress_callback:
|
|
285
|
-
progress_callback(model_name, False)
|
|
286
|
-
return False
|
|
287
|
-
|
|
288
|
-
def download_essential_model(self, progress_callback: Optional[Callable[[str, bool], None]] = None) -> bool:
|
|
289
|
-
"""Download the essential English model for immediate functionality."""
|
|
290
|
-
return self.download_model(self.ESSENTIAL_MODEL, progress_callback)
|
|
291
|
-
|
|
292
|
-
def list_available_models(self, language: Optional[str] = None) -> Dict[str, Any]:
|
|
293
|
-
"""Get list of available models with metadata.
|
|
294
|
-
|
|
295
|
-
Args:
|
|
296
|
-
language: Optional language filter
|
|
297
|
-
|
|
298
|
-
Returns:
|
|
299
|
-
dict: Model information in JSON-serializable format
|
|
300
|
-
"""
|
|
301
|
-
if language:
|
|
302
|
-
if language in self.AVAILABLE_MODELS:
|
|
303
|
-
return {language: self.AVAILABLE_MODELS[language]}
|
|
304
|
-
else:
|
|
305
|
-
return {}
|
|
306
|
-
|
|
307
|
-
# Return all models with cache status
|
|
308
|
-
result = {}
|
|
309
|
-
for lang, models in self.AVAILABLE_MODELS.items():
|
|
310
|
-
result[lang] = {}
|
|
311
|
-
for model_id, model_info in models.items():
|
|
312
|
-
# Add cache status to each model
|
|
313
|
-
model_data = model_info.copy()
|
|
314
|
-
model_data["cached"] = self.is_model_cached(model_info["model"])
|
|
315
|
-
result[lang][model_id] = model_data
|
|
316
|
-
|
|
317
|
-
return result
|
|
318
|
-
|
|
319
|
-
def get_cached_models(self) -> List[str]:
|
|
320
|
-
"""Get list of model names that are currently cached."""
|
|
321
|
-
if not os.path.exists(self.cache_dir):
|
|
322
|
-
return []
|
|
323
|
-
|
|
324
|
-
cached = []
|
|
325
|
-
try:
|
|
326
|
-
for item in os.listdir(self.cache_dir):
|
|
327
|
-
if item.startswith("tts_models--"):
|
|
328
|
-
# Convert cache name back to model name
|
|
329
|
-
model_name = item.replace("--", "/")
|
|
330
|
-
if self.is_model_cached(model_name):
|
|
331
|
-
cached.append(model_name)
|
|
332
|
-
except Exception as e:
|
|
333
|
-
if self.debug_mode:
|
|
334
|
-
print(f"Error listing cached models: {e}")
|
|
335
|
-
|
|
336
|
-
return cached
|
|
337
|
-
|
|
338
|
-
def get_status(self) -> Dict[str, Any]:
|
|
339
|
-
"""Get comprehensive status information."""
|
|
340
|
-
cached_models = self.get_cached_models()
|
|
341
|
-
essential_cached = self.ESSENTIAL_MODEL in cached_models
|
|
342
|
-
|
|
343
|
-
# Calculate total cache size
|
|
344
|
-
total_size_mb = 0
|
|
345
|
-
if os.path.exists(self.cache_dir):
|
|
346
|
-
try:
|
|
347
|
-
for root, dirs, files in os.walk(self.cache_dir):
|
|
348
|
-
for file in files:
|
|
349
|
-
total_size_mb += os.path.getsize(os.path.join(root, file)) / (1024 * 1024)
|
|
350
|
-
except:
|
|
351
|
-
pass
|
|
352
|
-
|
|
353
|
-
return {
|
|
354
|
-
"cache_dir": self.cache_dir,
|
|
355
|
-
"cached_models": cached_models,
|
|
356
|
-
"total_cached": len(cached_models),
|
|
357
|
-
"essential_model_cached": essential_cached,
|
|
358
|
-
"essential_model": self.ESSENTIAL_MODEL,
|
|
359
|
-
"ready_for_offline": essential_cached,
|
|
360
|
-
"total_size_mb": round(total_size_mb, 1),
|
|
361
|
-
"available_languages": list(self.AVAILABLE_MODELS.keys()),
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
def clear_cache(self, confirm: bool = False) -> bool:
|
|
365
|
-
"""Clear the model cache."""
|
|
366
|
-
if not confirm:
|
|
367
|
-
return False
|
|
368
|
-
|
|
369
|
-
try:
|
|
370
|
-
import shutil
|
|
371
|
-
if os.path.exists(self.cache_dir):
|
|
372
|
-
shutil.rmtree(self.cache_dir)
|
|
373
|
-
if self.debug_mode:
|
|
374
|
-
print(f"✅ Cleared model cache: {self.cache_dir}")
|
|
375
|
-
return True
|
|
376
|
-
return True
|
|
377
|
-
except Exception as e:
|
|
378
|
-
if self.debug_mode:
|
|
379
|
-
print(f"❌ Failed to clear cache: {e}")
|
|
380
|
-
return False
|
|
381
|
-
|
|
382
|
-
def ensure_essential_model(self, auto_download: bool = True) -> bool:
|
|
383
|
-
"""Ensure the essential model is available.
|
|
384
|
-
|
|
385
|
-
Args:
|
|
386
|
-
auto_download: Whether to download if not cached
|
|
387
|
-
|
|
388
|
-
Returns:
|
|
389
|
-
bool: True if essential model is ready
|
|
390
|
-
"""
|
|
391
|
-
if self.is_model_cached(self.ESSENTIAL_MODEL):
|
|
392
|
-
return True
|
|
393
|
-
|
|
394
|
-
if not auto_download:
|
|
395
|
-
return False
|
|
396
|
-
|
|
397
|
-
return self.download_essential_model()
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
# Global instance for easy access
|
|
401
|
-
_model_manager = None
|
|
402
|
-
|
|
403
|
-
def get_model_manager(debug_mode: bool = False) -> SimpleModelManager:
|
|
404
|
-
"""Get the global model manager instance."""
|
|
405
|
-
global _model_manager
|
|
406
|
-
if _model_manager is None:
|
|
407
|
-
_model_manager = SimpleModelManager(debug_mode=debug_mode)
|
|
408
|
-
return _model_manager
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
# Simple API functions for third-party use
|
|
412
|
-
def list_models(language: Optional[str] = None) -> str:
|
|
413
|
-
"""Get available models as JSON string.
|
|
414
|
-
|
|
415
|
-
Args:
|
|
416
|
-
language: Optional language filter
|
|
417
|
-
|
|
418
|
-
Returns:
|
|
419
|
-
str: JSON string of available models
|
|
420
|
-
"""
|
|
421
|
-
manager = get_model_manager()
|
|
422
|
-
return json.dumps(manager.list_available_models(language), indent=2)
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
def download_model(model_name: str, progress_callback: Optional[Callable[[str, bool], None]] = None) -> bool:
|
|
426
|
-
"""Download a specific model.
|
|
427
|
-
|
|
428
|
-
Args:
|
|
429
|
-
model_name: Model name or voice ID (e.g., 'en.vits' or 'tts_models/en/ljspeech/vits')
|
|
430
|
-
progress_callback: Optional progress callback
|
|
431
|
-
|
|
432
|
-
Returns:
|
|
433
|
-
bool: True if successful
|
|
434
|
-
"""
|
|
435
|
-
manager = get_model_manager()
|
|
436
|
-
|
|
437
|
-
# Handle voice ID format (e.g., 'en.vits')
|
|
438
|
-
if '.' in model_name and not model_name.startswith('tts_models'):
|
|
439
|
-
lang, voice_id = model_name.split('.', 1)
|
|
440
|
-
if lang in manager.AVAILABLE_MODELS and voice_id in manager.AVAILABLE_MODELS[lang]:
|
|
441
|
-
model_name = manager.AVAILABLE_MODELS[lang][voice_id]["model"]
|
|
442
|
-
else:
|
|
443
|
-
return False
|
|
444
|
-
|
|
445
|
-
return manager.download_model(model_name, progress_callback)
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
def get_status() -> str:
|
|
449
|
-
"""Get model cache status as JSON string."""
|
|
450
|
-
manager = get_model_manager()
|
|
451
|
-
return json.dumps(manager.get_status(), indent=2)
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
def is_ready() -> bool:
|
|
455
|
-
"""Check if essential model is ready for immediate use."""
|
|
456
|
-
manager = get_model_manager()
|
|
457
|
-
return manager.is_model_cached(manager.ESSENTIAL_MODEL)
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
def download_models_cli():
|
|
461
|
-
"""Simple CLI entry point for downloading models."""
|
|
462
|
-
import argparse
|
|
463
|
-
import sys
|
|
464
|
-
|
|
465
|
-
parser = argparse.ArgumentParser(description="Download TTS models for offline use")
|
|
466
|
-
parser.add_argument("--essential", action="store_true",
|
|
467
|
-
help="Download essential model (default)")
|
|
468
|
-
parser.add_argument("--all", action="store_true",
|
|
469
|
-
help="Download all available models")
|
|
470
|
-
parser.add_argument("--model", type=str,
|
|
471
|
-
help="Download specific model by name")
|
|
472
|
-
parser.add_argument("--language", type=str,
|
|
473
|
-
help="Download models for specific language (en, fr, es, de, it)")
|
|
474
|
-
parser.add_argument("--status", action="store_true",
|
|
475
|
-
help="Show current cache status")
|
|
476
|
-
parser.add_argument("--clear", action="store_true",
|
|
477
|
-
help="Clear model cache")
|
|
478
|
-
|
|
479
|
-
args = parser.parse_args()
|
|
480
|
-
|
|
481
|
-
manager = get_model_manager(debug_mode=True)
|
|
482
|
-
|
|
483
|
-
if args.status:
|
|
484
|
-
print(get_status())
|
|
485
|
-
return
|
|
486
|
-
|
|
487
|
-
if args.clear:
|
|
488
|
-
# Ask for confirmation
|
|
489
|
-
response = input("⚠️ This will delete all downloaded TTS models. Continue? (y/N): ")
|
|
490
|
-
if response.lower() == 'y':
|
|
491
|
-
success = manager.clear_cache(confirm=True)
|
|
492
|
-
if success:
|
|
493
|
-
print("✅ Model cache cleared")
|
|
494
|
-
else:
|
|
495
|
-
print("❌ Failed to clear cache")
|
|
496
|
-
else:
|
|
497
|
-
print("Cancelled")
|
|
498
|
-
return
|
|
499
|
-
|
|
500
|
-
if args.model:
|
|
501
|
-
success = download_model(args.model)
|
|
502
|
-
if success:
|
|
503
|
-
print(f"✅ Downloaded {args.model}")
|
|
504
|
-
else:
|
|
505
|
-
print(f"❌ Failed to download {args.model}")
|
|
506
|
-
sys.exit(0 if success else 1)
|
|
507
|
-
|
|
508
|
-
if args.language:
|
|
509
|
-
# Language-specific downloads using our simple API
|
|
510
|
-
lang_models = {
|
|
511
|
-
'en': ['en.tacotron2', 'en.jenny', 'en.ek1'],
|
|
512
|
-
'fr': ['fr.css10_vits', 'fr.mai_tacotron2'],
|
|
513
|
-
'es': ['es.mai_tacotron2'],
|
|
514
|
-
'de': ['de.thorsten_vits'],
|
|
515
|
-
'it': ['it.mai_male_vits', 'it.mai_female_vits']
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
if args.language not in lang_models:
|
|
519
|
-
print(f"❌ Language '{args.language}' not supported")
|
|
520
|
-
print(f" Available: {list(lang_models.keys())}")
|
|
521
|
-
sys.exit(1)
|
|
522
|
-
|
|
523
|
-
success = False
|
|
524
|
-
for model_id in lang_models[args.language]:
|
|
525
|
-
if download_model(model_id):
|
|
526
|
-
print(f"✅ Downloaded {model_id}")
|
|
527
|
-
success = True
|
|
528
|
-
break
|
|
529
|
-
|
|
530
|
-
sys.exit(0 if success else 1)
|
|
531
|
-
|
|
532
|
-
# Default: download essential model
|
|
533
|
-
print("📦 Downloading essential TTS model...")
|
|
534
|
-
success = download_model(manager.ESSENTIAL_MODEL)
|
|
535
|
-
if success:
|
|
536
|
-
print("✅ Essential model ready!")
|
|
537
|
-
else:
|
|
538
|
-
print("❌ Failed to download essential model")
|
|
539
|
-
sys.exit(0 if success else 1)
|