abstractvoice 0.5.2__py3-none-any.whl → 0.6.1__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.
Files changed (51) hide show
  1. abstractvoice/__init__.py +2 -5
  2. abstractvoice/__main__.py +82 -3
  3. abstractvoice/adapters/__init__.py +12 -0
  4. abstractvoice/adapters/base.py +207 -0
  5. abstractvoice/adapters/stt_faster_whisper.py +401 -0
  6. abstractvoice/adapters/tts_piper.py +480 -0
  7. abstractvoice/aec/__init__.py +10 -0
  8. abstractvoice/aec/webrtc_apm.py +56 -0
  9. abstractvoice/artifacts.py +173 -0
  10. abstractvoice/audio/__init__.py +7 -0
  11. abstractvoice/audio/recorder.py +46 -0
  12. abstractvoice/audio/resample.py +25 -0
  13. abstractvoice/cloning/__init__.py +7 -0
  14. abstractvoice/cloning/engine_chroma.py +738 -0
  15. abstractvoice/cloning/engine_f5.py +546 -0
  16. abstractvoice/cloning/manager.py +349 -0
  17. abstractvoice/cloning/store.py +362 -0
  18. abstractvoice/compute/__init__.py +6 -0
  19. abstractvoice/compute/device.py +73 -0
  20. abstractvoice/config/__init__.py +2 -0
  21. abstractvoice/config/voice_catalog.py +19 -0
  22. abstractvoice/dependency_check.py +0 -1
  23. abstractvoice/examples/cli_repl.py +2403 -243
  24. abstractvoice/examples/voice_cli.py +64 -63
  25. abstractvoice/integrations/__init__.py +2 -0
  26. abstractvoice/integrations/abstractcore.py +116 -0
  27. abstractvoice/integrations/abstractcore_plugin.py +253 -0
  28. abstractvoice/prefetch.py +82 -0
  29. abstractvoice/recognition.py +424 -42
  30. abstractvoice/stop_phrase.py +103 -0
  31. abstractvoice/tts/__init__.py +3 -3
  32. abstractvoice/tts/adapter_tts_engine.py +210 -0
  33. abstractvoice/tts/tts_engine.py +257 -1208
  34. abstractvoice/vm/__init__.py +2 -0
  35. abstractvoice/vm/common.py +21 -0
  36. abstractvoice/vm/core.py +139 -0
  37. abstractvoice/vm/manager.py +108 -0
  38. abstractvoice/vm/stt_mixin.py +158 -0
  39. abstractvoice/vm/tts_mixin.py +550 -0
  40. abstractvoice/voice_manager.py +6 -1061
  41. abstractvoice-0.6.1.dist-info/METADATA +213 -0
  42. abstractvoice-0.6.1.dist-info/RECORD +52 -0
  43. {abstractvoice-0.5.2.dist-info → abstractvoice-0.6.1.dist-info}/WHEEL +1 -1
  44. abstractvoice-0.6.1.dist-info/entry_points.txt +6 -0
  45. abstractvoice/instant_setup.py +0 -83
  46. abstractvoice/simple_model_manager.py +0 -539
  47. abstractvoice-0.5.2.dist-info/METADATA +0 -1458
  48. abstractvoice-0.5.2.dist-info/RECORD +0 -23
  49. abstractvoice-0.5.2.dist-info/entry_points.txt +0 -2
  50. {abstractvoice-0.5.2.dist-info → abstractvoice-0.6.1.dist-info}/licenses/LICENSE +0 -0
  51. {abstractvoice-0.5.2.dist-info → abstractvoice-0.6.1.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)