abstractvoice 0.3.0__py3-none-any.whl → 0.4.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.
abstractvoice/__init__.py CHANGED
@@ -29,5 +29,8 @@ warnings.filterwarnings(
29
29
  # Import the main class for public API
30
30
  from .voice_manager import VoiceManager
31
31
 
32
- __version__ = "0.2.0"
33
- __all__ = ['VoiceManager']
32
+ # Import simple APIs for third-party applications
33
+ from .simple_model_manager import list_models, download_model, get_status, is_ready
34
+
35
+ __version__ = "0.4.1"
36
+ __all__ = ['VoiceManager', 'list_models', 'download_model', 'get_status', 'is_ready']
@@ -38,7 +38,7 @@ class VoiceREPL(cmd.Cmd):
38
38
  use_rawinput = True
39
39
 
40
40
  def __init__(self, api_url="http://localhost:11434/api/chat",
41
- model="granite3.3:2b", debug_mode=False, language="en", tts_model=None):
41
+ model="granite3.3:2b", debug_mode=False, language="en", tts_model=None, disable_tts=False):
42
42
  super().__init__()
43
43
 
44
44
  # Debug mode
@@ -54,11 +54,15 @@ class VoiceREPL(cmd.Cmd):
54
54
  self.current_language = language
55
55
 
56
56
  # Initialize voice manager with language support
57
- self.voice_manager = VoiceManager(
58
- language=language,
59
- tts_model=tts_model,
60
- debug_mode=debug_mode
61
- )
57
+ if disable_tts:
58
+ self.voice_manager = None
59
+ print("🔇 TTS disabled - text-only mode")
60
+ else:
61
+ self.voice_manager = VoiceManager(
62
+ language=language,
63
+ tts_model=tts_model,
64
+ debug_mode=debug_mode
65
+ )
62
66
 
63
67
  # Settings
64
68
  self.use_tts = True
@@ -90,8 +94,11 @@ class VoiceREPL(cmd.Cmd):
90
94
  def _get_intro(self):
91
95
  """Generate intro message with help."""
92
96
  intro = f"\n{Colors.BOLD}Welcome to AbstractVoice CLI REPL{Colors.END}\n"
93
- lang_name = self.voice_manager.get_language_name()
94
- intro += f"API: {self.api_url} | Model: {self.model} | Voice: {lang_name}\n"
97
+ if self.voice_manager:
98
+ lang_name = self.voice_manager.get_language_name()
99
+ intro += f"API: {self.api_url} | Model: {self.model} | Voice: {lang_name}\n"
100
+ else:
101
+ intro += f"API: {self.api_url} | Model: {self.model} | Voice: Disabled\n"
95
102
  intro += f"\n{Colors.CYAN}Quick Start:{Colors.END}\n"
96
103
  intro += " • Type messages to chat with the LLM\n"
97
104
  intro += " • Use /voice <mode> to enable voice input\n"
@@ -232,7 +239,7 @@ class VoiceREPL(cmd.Cmd):
232
239
  print(f"{Colors.CYAN}{response_text}{Colors.END}")
233
240
 
234
241
  # Speak the response if voice manager is available
235
- if self.voice_manager:
242
+ if self.voice_manager and self.use_tts:
236
243
  self.voice_manager.speak(response_text)
237
244
 
238
245
  except requests.exceptions.ConnectionError as e:
@@ -376,18 +383,47 @@ class VoiceREPL(cmd.Cmd):
376
383
  /setvoice <voice_id> # Set voice (format: language.voice_id)
377
384
 
378
385
  Examples:
379
- /setvoice # List all voices
386
+ /setvoice # List all voices with JSON-like info
380
387
  /setvoice fr.css10_vits # Set French CSS10 VITS voice
381
388
  /setvoice it.mai_male_vits # Set Italian male VITS voice
382
389
  """
383
390
  if not args:
384
- # Show all available voices organized by language
391
+ # Show all available voices with metadata
385
392
  print(f"\n{Colors.CYAN}Available Voice Models:{Colors.END}")
386
- self.voice_manager.list_voices()
387
393
 
388
- print(f"\n{Colors.YELLOW}Usage:{Colors.END}")
389
- print(" /setvoice <language>.<voice_id>")
390
- print(" Example: /setvoice fr.css10_vits")
394
+ try:
395
+ models = self.voice_manager.list_available_models()
396
+
397
+ for language, voices in models.items():
398
+ # Get language name
399
+ lang_names = {
400
+ 'en': 'English', 'fr': 'French', 'es': 'Spanish',
401
+ 'de': 'German', 'it': 'Italian'
402
+ }
403
+ lang_name = lang_names.get(language, language.upper())
404
+
405
+ print(f"\n🌍 {lang_name} ({language}):")
406
+
407
+ for voice_id, voice_info in voices.items():
408
+ cached_icon = "✅" if voice_info.get('cached', False) else "📥"
409
+ quality_icon = "✨" if voice_info['quality'] == 'excellent' else "🔧"
410
+ size_text = f"{voice_info['size_mb']}MB"
411
+
412
+ print(f" {cached_icon} {quality_icon} {language}.{voice_id}")
413
+ print(f" {voice_info['name']} ({size_text})")
414
+ print(f" {voice_info['description']}")
415
+ if voice_info.get('requires_espeak', False):
416
+ print(f" ⚠️ Requires espeak-ng")
417
+
418
+ print(f"\n{Colors.YELLOW}Usage:{Colors.END}")
419
+ print(" /setvoice <language>.<voice_id>")
420
+ print(" Example: /setvoice fr.css10_vits")
421
+ print("\n📥 = Download needed ✅ = Ready ✨ = High quality 🔧 = Good quality")
422
+
423
+ except Exception as e:
424
+ print(f"❌ Error listing models: {e}")
425
+ # Fallback to old method
426
+ self.voice_manager.list_voices()
391
427
  return
392
428
 
393
429
  voice_spec = args.strip()
@@ -412,45 +448,46 @@ class VoiceREPL(cmd.Cmd):
412
448
  else:
413
449
  was_active = False
414
450
 
415
- # Set the specific voice
451
+ # Download and set the specific voice using programmatic API
416
452
  try:
417
- success = self.voice_manager.set_voice(language, voice_id)
453
+ print(f"🔄 Setting voice {voice_spec}...")
454
+
455
+ # Use the programmatic download API
456
+ success = self.voice_manager.download_model(voice_spec)
457
+
418
458
  if success:
419
- # Update current language to match the voice
420
- self.current_language = language
459
+ # Now set the language to match
460
+ success = self.voice_manager.set_language(language)
421
461
 
422
- # Get voice info for confirmation
423
- voice_info = self.voice_manager.VOICE_CATALOG.get(language, {}).get(voice_id, {})
424
- lang_name = self.voice_manager.get_language_name(language)
462
+ if success:
463
+ # Update current language
464
+ self.current_language = language
425
465
 
426
- print(f"✅ Voice changed successfully!")
427
- print(f" Language: {lang_name} ({language})")
428
- print(f" Voice: {voice_id}")
429
- if voice_info:
430
- quality_icon = "✨" if voice_info.get('quality') == 'premium' else "🔧"
431
- gender_icon = {"male": "👨", "female": "👩", "multiple": "👥"}.get(voice_info.get('gender'), "🗣️")
432
- print(f" Details: {quality_icon} {gender_icon} {voice_info.get('accent', 'Unknown accent')}")
466
+ print(f"✅ Voice set to {voice_spec}")
433
467
 
434
- # Test the new voice
435
- test_messages = {
436
- 'en': "Voice changed to English.",
437
- 'fr': "Voix changée en français.",
438
- 'es': "Voz cambiada al español.",
439
- 'de': "Stimme auf Deutsch geändert.",
440
- 'it': "Voce cambiata in italiano."
441
- }
442
- test_msg = test_messages.get(language, "Voice changed successfully.")
443
- self.voice_manager.speak(test_msg)
468
+ # Test the voice
469
+ test_messages = {
470
+ 'en': 'Voice changed to English.',
471
+ 'fr': 'Voix changée en français.',
472
+ 'es': 'Voz cambiada al español.',
473
+ 'de': 'Stimme auf Deutsch geändert.',
474
+ 'it': 'Voce cambiata in italiano.'
475
+ }
476
+ test_msg = test_messages.get(language, f'Voice changed to {language}.')
477
+ self.voice_manager.speak(test_msg)
444
478
 
445
- # Restart voice mode if it was active
446
- if was_active:
447
- self.do_voice(self.voice_mode)
479
+ # Restart voice mode if it was active
480
+ if was_active:
481
+ self.do_voice(self.voice_mode)
482
+ else:
483
+ print(f"❌ Failed to set language: {language}")
448
484
  else:
449
- print(f"❌ Failed to set voice: {voice_spec}")
450
- print(f" Run '/setvoice' to see available voices")
485
+ print(f"❌ Failed to download voice: {voice_spec}")
486
+ print(" Check your internet connection or try a different voice")
451
487
 
452
488
  except Exception as e:
453
489
  print(f"❌ Error setting voice: {e}")
490
+ print(f" Run '/setvoice' to see available voices")
454
491
  if self.debug_mode:
455
492
  import traceback
456
493
  traceback.print_exc()
@@ -11,18 +11,20 @@ from abstractvoice.examples.cli_repl import VoiceREPL
11
11
 
12
12
  def print_examples():
13
13
  """Print available examples."""
14
- print("Available examples:")
15
- print(" cli - Command-line REPL example")
16
- print(" web - Web API example")
17
- print(" simple - Simple usage example")
18
- print(" check-deps - Check dependency compatibility")
19
- print("\nUsage: abstractvoice <example> [--language <lang>] [args...]")
14
+ print("Available commands:")
15
+ print(" cli - Command-line REPL example")
16
+ print(" web - Web API example")
17
+ print(" simple - Simple usage example")
18
+ print(" check-deps - Check dependency compatibility")
19
+ print(" download-models - Download TTS models for offline use")
20
+ print("\nUsage: abstractvoice <command> [--language <lang>] [args...]")
20
21
  print("\nSupported languages: en, fr, es, de, it, ru, multilingual")
21
22
  print("\nExamples:")
22
- print(" abstractvoice cli --language fr # French CLI")
23
- print(" abstractvoice simple --language ru # Russian simple example")
24
- print(" abstractvoice check-deps # Check dependencies")
25
- print(" abstractvoice # Direct voice mode (default)")
23
+ print(" abstractvoice cli --language fr # French CLI")
24
+ print(" abstractvoice simple --language ru # Russian simple example")
25
+ print(" abstractvoice check-deps # Check dependencies")
26
+ print(" abstractvoice download-models # Download models for offline use")
27
+ print(" abstractvoice # Direct voice mode (default)")
26
28
 
27
29
  def simple_example():
28
30
  """Run a simple example demonstrating basic usage."""
@@ -95,10 +97,22 @@ def simple_example():
95
97
 
96
98
  def parse_args():
97
99
  """Parse command line arguments."""
100
+ import sys
101
+
102
+ # Check if it's a download-models command and handle separately
103
+ if len(sys.argv) > 1 and sys.argv[1] == "download-models":
104
+ # Return early with just the command to handle in main()
105
+ class DownloadModelsArgs:
106
+ command = "download-models"
107
+ # Add dummy attributes to prevent AttributeError
108
+ model = "granite3.3:2b"
109
+ debug = False
110
+ return DownloadModelsArgs()
111
+
98
112
  parser = argparse.ArgumentParser(description="AbstractVoice - Voice interactions with AI")
99
113
 
100
114
  # Examples and special commands
101
- parser.add_argument("command", nargs="?", help="Command to run: cli, web, simple, check-deps (default: voice mode)")
115
+ parser.add_argument("command", nargs="?", help="Command to run: cli, web, simple, check-deps, download-models (default: voice mode)")
102
116
 
103
117
  # Voice mode arguments
104
118
  parser.add_argument("--debug", action="store_true", help="Enable debug mode")
@@ -110,6 +124,8 @@ def parse_args():
110
124
  help="Whisper model to use (tiny, base, small, medium, large)")
111
125
  parser.add_argument("--no-listening", action="store_true",
112
126
  help="Disable speech-to-text (listening), TTS still works")
127
+ parser.add_argument("--no-tts", action="store_true",
128
+ help="Disable text-to-speech (TTS), text-only mode")
113
129
  parser.add_argument("--system",
114
130
  help="Custom system prompt")
115
131
  parser.add_argument("--temperature", type=float, default=0.4,
@@ -141,6 +157,17 @@ def main():
141
157
  import traceback
142
158
  traceback.print_exc()
143
159
  return
160
+ elif args.command == "download-models":
161
+ from abstractvoice.model_manager import download_models_cli
162
+ # Pass remaining arguments to download_models_cli
163
+ import sys
164
+ original_argv = sys.argv
165
+ sys.argv = ["download-models"] + sys.argv[2:] # Remove script name and "download-models"
166
+ try:
167
+ download_models_cli()
168
+ finally:
169
+ sys.argv = original_argv
170
+ return
144
171
  elif args.command == "cli":
145
172
  # Import and run CLI REPL example
146
173
  repl = VoiceREPL(
@@ -188,7 +215,8 @@ def main():
188
215
  model=args.model,
189
216
  debug_mode=args.debug,
190
217
  language=args.language,
191
- tts_model=args.tts_model
218
+ tts_model=args.tts_model,
219
+ disable_tts=args.no_tts
192
220
  )
193
221
 
194
222
  # Set custom system prompt if provided
@@ -224,22 +252,30 @@ def main():
224
252
  print("\nExiting AbstractVoice...")
225
253
  except Exception as e:
226
254
  error_msg = str(e).lower()
227
- if "model file not found" in error_msg or "no such file" in error_msg:
228
- print(f"❌ Model '{args.model}' not found")
229
- print(f" The model file is missing or not fully downloaded")
230
- print(f" Try: ollama pull {args.model}")
231
- print(f" Or check available models: ollama list")
232
- elif "connection" in error_msg or "refused" in error_msg:
233
- print(f" Cannot connect to Ollama")
255
+
256
+ # Check if it's a TTS-related error (not Ollama model error)
257
+ if "model file not found in the output path" in error_msg:
258
+ print(f" TTS model download failed")
259
+ print(f" This is a TTS voice model issue, not your Ollama model")
260
+ print(f" Your Ollama model '{args.model}' is fine")
261
+ print(f" Try: rm -rf ~/.cache/tts && pip install --force-reinstall coqui-tts")
262
+ print(f" Or check network connectivity for model downloads")
263
+ elif "ollama" in error_msg or "11434" in error_msg:
264
+ print(f"❌ Cannot connect to Ollama at {args.api}")
234
265
  print(f" Make sure Ollama is running: ollama serve")
235
- print(f" API URL: {args.api}")
266
+ print(f" Your model '{args.model}' exists but Ollama server isn't responding")
236
267
  elif "importerror" in error_msg or "no module" in error_msg:
237
268
  print(f"❌ Missing dependencies")
238
269
  print(f" Try running: abstractvoice check-deps")
239
270
  print(f" Or install dependencies: pip install abstractvoice[voice-full]")
271
+ elif "espeak" in error_msg or "phoneme" in error_msg:
272
+ print(f"❌ Voice synthesis setup issue")
273
+ print(f" Install espeak-ng for better voice quality: brew install espeak-ng")
274
+ print(f" Or this might be a TTS model download issue")
240
275
  else:
241
276
  print(f"❌ Application error: {e}")
242
277
  print(f" Try running with --debug for more details")
278
+ print(f" Note: Your Ollama model '{args.model}' appears to be available")
243
279
 
244
280
  if args.debug:
245
281
  import traceback