abstractvoice 0.3.1__py3-none-any.whl → 0.4.6__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 +5 -2
- abstractvoice/examples/cli_repl.py +81 -44
- abstractvoice/examples/voice_cli.py +56 -20
- abstractvoice/instant_setup.py +83 -0
- abstractvoice/simple_model_manager.py +500 -0
- abstractvoice/tts/tts_engine.py +253 -23
- abstractvoice/voice_manager.py +176 -21
- {abstractvoice-0.3.1.dist-info → abstractvoice-0.4.6.dist-info}/METADATA +125 -19
- abstractvoice-0.4.6.dist-info/RECORD +23 -0
- abstractvoice-0.3.1.dist-info/RECORD +0 -21
- {abstractvoice-0.3.1.dist-info → abstractvoice-0.4.6.dist-info}/WHEEL +0 -0
- {abstractvoice-0.3.1.dist-info → abstractvoice-0.4.6.dist-info}/entry_points.txt +0 -0
- {abstractvoice-0.3.1.dist-info → abstractvoice-0.4.6.dist-info}/licenses/LICENSE +0 -0
- {abstractvoice-0.3.1.dist-info → abstractvoice-0.4.6.dist-info}/top_level.txt +0 -0
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
|
-
|
|
33
|
-
|
|
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.6"
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
94
|
-
|
|
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
|
|
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
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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
|
-
#
|
|
451
|
+
# Download and set the specific voice using programmatic API
|
|
416
452
|
try:
|
|
417
|
-
|
|
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
|
-
#
|
|
420
|
-
|
|
459
|
+
# Now set the language to match
|
|
460
|
+
success = self.voice_manager.set_language(language)
|
|
421
461
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
462
|
+
if success:
|
|
463
|
+
# Update current language
|
|
464
|
+
self.current_language = language
|
|
425
465
|
|
|
426
|
-
|
|
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
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
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
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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
|
|
450
|
-
print(
|
|
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
|
|
15
|
-
print(" cli
|
|
16
|
-
print(" web
|
|
17
|
-
print(" simple
|
|
18
|
-
print(" check-deps
|
|
19
|
-
print("
|
|
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
|
|
23
|
-
print(" abstractvoice simple --language ru
|
|
24
|
-
print(" abstractvoice check-deps
|
|
25
|
-
print(" abstractvoice
|
|
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.simple_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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
print(f"
|
|
231
|
-
print(f"
|
|
232
|
-
|
|
233
|
-
print(f"
|
|
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"
|
|
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
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Instant Setup Module for AbstractVoice
|
|
3
|
+
Provides immediate TTS functionality with seamless model download.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
# Essential model for instant functionality (lightweight, reliable)
|
|
11
|
+
ESSENTIAL_MODEL = "tts_models/en/ljspeech/fast_pitch"
|
|
12
|
+
ESSENTIAL_MODEL_SIZE = "~100MB"
|
|
13
|
+
|
|
14
|
+
def ensure_instant_tts():
|
|
15
|
+
"""
|
|
16
|
+
Ensure TTS is ready for immediate use.
|
|
17
|
+
Downloads essential model if needed with progress indicator.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
bool: True if TTS is ready, False if failed
|
|
21
|
+
"""
|
|
22
|
+
try:
|
|
23
|
+
from TTS.api import TTS
|
|
24
|
+
from TTS.utils.manage import ModelManager
|
|
25
|
+
|
|
26
|
+
manager = ModelManager()
|
|
27
|
+
|
|
28
|
+
# Check if essential model is already cached
|
|
29
|
+
if is_model_cached(ESSENTIAL_MODEL):
|
|
30
|
+
return True
|
|
31
|
+
|
|
32
|
+
# Download essential model with user-friendly progress
|
|
33
|
+
print(f"🚀 AbstractVoice: Setting up TTS ({ESSENTIAL_MODEL_SIZE})...")
|
|
34
|
+
print(f" This happens once and takes ~30 seconds")
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
# Download with progress bar
|
|
38
|
+
tts = TTS(model_name=ESSENTIAL_MODEL, progress_bar=True)
|
|
39
|
+
print(f"✅ TTS ready! AbstractVoice is now fully functional.")
|
|
40
|
+
return True
|
|
41
|
+
|
|
42
|
+
except Exception as e:
|
|
43
|
+
print(f"❌ Setup failed: {e}")
|
|
44
|
+
print(f"💡 Try: pip install abstractvoice[all]")
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
except ImportError as e:
|
|
48
|
+
print(f"❌ Missing dependencies: {e}")
|
|
49
|
+
print(f"💡 Install with: pip install abstractvoice[all]")
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
def is_model_cached(model_name):
|
|
53
|
+
"""Check if a model is already cached."""
|
|
54
|
+
try:
|
|
55
|
+
from TTS.utils.manage import ModelManager
|
|
56
|
+
manager = ModelManager()
|
|
57
|
+
|
|
58
|
+
# Get cached models list
|
|
59
|
+
models_file = os.path.join(manager.output_prefix, ".models.json")
|
|
60
|
+
if os.path.exists(models_file):
|
|
61
|
+
import json
|
|
62
|
+
with open(models_file, 'r') as f:
|
|
63
|
+
cached_models = json.load(f)
|
|
64
|
+
return model_name in cached_models
|
|
65
|
+
|
|
66
|
+
# Fallback: check if model directory exists and has content
|
|
67
|
+
model_dir = model_name.replace("/", "--")
|
|
68
|
+
model_path = os.path.join(manager.output_prefix, model_dir)
|
|
69
|
+
return os.path.exists(model_path) and bool(os.listdir(model_path))
|
|
70
|
+
|
|
71
|
+
except:
|
|
72
|
+
# If anything fails, assume not cached
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
def get_instant_model():
|
|
76
|
+
"""Get the essential model name for instant setup."""
|
|
77
|
+
return ESSENTIAL_MODEL
|
|
78
|
+
|
|
79
|
+
if __name__ == "__main__":
|
|
80
|
+
# CLI test
|
|
81
|
+
print("🧪 Testing instant setup...")
|
|
82
|
+
success = ensure_instant_tts()
|
|
83
|
+
print(f"Result: {'✅ Ready' if success else '❌ Failed'}")
|