abstractvoice 0.1.1__py3-none-any.whl → 0.2.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/__main__.py +33 -11
- abstractvoice/dependency_check.py +274 -0
- abstractvoice/examples/cli_repl.py +198 -13
- abstractvoice/examples/voice_cli.py +20 -6
- abstractvoice/recognition.py +50 -7
- abstractvoice/stt/transcriber.py +17 -2
- abstractvoice/tts/tts_engine.py +138 -32
- abstractvoice/vad/voice_detector.py +16 -2
- abstractvoice/voice_manager.py +558 -16
- {abstractvoice-0.1.1.dist-info → abstractvoice-0.2.1.dist-info}/METADATA +196 -50
- abstractvoice-0.2.1.dist-info/RECORD +21 -0
- {abstractvoice-0.1.1.dist-info → abstractvoice-0.2.1.dist-info}/licenses/LICENSE +1 -1
- abstractvoice-0.1.1.dist-info/RECORD +0 -20
- {abstractvoice-0.1.1.dist-info → abstractvoice-0.2.1.dist-info}/WHEEL +0 -0
- {abstractvoice-0.1.1.dist-info → abstractvoice-0.2.1.dist-info}/entry_points.txt +0 -0
- {abstractvoice-0.1.1.dist-info → abstractvoice-0.2.1.dist-info}/top_level.txt +0 -0
abstractvoice/__main__.py
CHANGED
|
@@ -15,20 +15,27 @@ def print_examples():
|
|
|
15
15
|
print(" cli - Command-line REPL example")
|
|
16
16
|
print(" web - Web API example")
|
|
17
17
|
print(" simple - Simple usage example")
|
|
18
|
-
print("
|
|
18
|
+
print(" check-deps - Check dependency compatibility")
|
|
19
|
+
print("\nUsage: python -m abstractvoice <example> [--language <lang>] [args...]")
|
|
20
|
+
print("\nSupported languages: en, fr, es, de, it, ru, multilingual")
|
|
21
|
+
print("\nExamples:")
|
|
22
|
+
print(" python -m abstractvoice cli --language fr # French CLI")
|
|
23
|
+
print(" python -m abstractvoice simple --language ru # Russian simple example")
|
|
24
|
+
print(" python -m abstractvoice check-deps # Check dependencies")
|
|
19
25
|
|
|
20
26
|
|
|
21
27
|
def simple_example():
|
|
22
28
|
"""Run a simple example demonstrating basic usage."""
|
|
23
29
|
from abstractvoice import VoiceManager
|
|
24
30
|
import time
|
|
25
|
-
|
|
31
|
+
|
|
26
32
|
print("Simple AbstractVoice Example")
|
|
27
33
|
print("============================")
|
|
28
34
|
print("This example demonstrates basic TTS and STT functionality.")
|
|
35
|
+
print("(Use --language argument to test different languages)")
|
|
29
36
|
print()
|
|
30
|
-
|
|
31
|
-
# Initialize voice manager
|
|
37
|
+
|
|
38
|
+
# Initialize voice manager (can be overridden with --language)
|
|
32
39
|
manager = VoiceManager(debug_mode=True)
|
|
33
40
|
|
|
34
41
|
try:
|
|
@@ -90,18 +97,33 @@ def simple_example():
|
|
|
90
97
|
def main():
|
|
91
98
|
"""Main entry point."""
|
|
92
99
|
parser = argparse.ArgumentParser(description="AbstractVoice examples")
|
|
93
|
-
parser.add_argument("example", nargs="?", help="Example to run (cli, web, simple)")
|
|
94
|
-
|
|
95
|
-
|
|
100
|
+
parser.add_argument("example", nargs="?", help="Example to run (cli, web, simple, check-deps)")
|
|
101
|
+
parser.add_argument("--language", "--lang", default="en",
|
|
102
|
+
choices=["en", "fr", "es", "de", "it", "ru", "multilingual"],
|
|
103
|
+
help="Voice language for examples")
|
|
104
|
+
|
|
105
|
+
# Parse just the first argument and language
|
|
96
106
|
args, remaining = parser.parse_known_args()
|
|
97
|
-
|
|
107
|
+
|
|
98
108
|
if not args.example:
|
|
99
109
|
print_examples()
|
|
100
110
|
return
|
|
101
|
-
|
|
102
|
-
#
|
|
111
|
+
|
|
112
|
+
# Handle check-deps specially (doesn't need language)
|
|
113
|
+
if args.example == "check-deps":
|
|
114
|
+
from abstractvoice.dependency_check import check_dependencies
|
|
115
|
+
try:
|
|
116
|
+
check_dependencies(verbose=True)
|
|
117
|
+
except Exception as e:
|
|
118
|
+
print(f"❌ Error running dependency check: {e}")
|
|
119
|
+
print("This might indicate a dependency issue.")
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
# Set remaining args as sys.argv for the examples, including language
|
|
123
|
+
if args.language != "en":
|
|
124
|
+
remaining = ["--language", args.language] + remaining
|
|
103
125
|
sys.argv = [sys.argv[0]] + remaining
|
|
104
|
-
|
|
126
|
+
|
|
105
127
|
if args.example == "cli":
|
|
106
128
|
from abstractvoice.examples.cli_repl import main
|
|
107
129
|
main()
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"""Dependency compatibility checker for AbstractVoice.
|
|
2
|
+
|
|
3
|
+
This module provides utilities to check dependency versions and compatibility,
|
|
4
|
+
helping users diagnose and resolve installation issues.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
import importlib
|
|
9
|
+
from typing import Dict, List, Tuple, Optional
|
|
10
|
+
import warnings
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DependencyChecker:
|
|
14
|
+
"""Check and validate AbstractVoice dependencies."""
|
|
15
|
+
|
|
16
|
+
# Known compatible version ranges
|
|
17
|
+
PYTORCH_COMPAT = {
|
|
18
|
+
"torch": ("2.0.0", "2.4.0"),
|
|
19
|
+
"torchvision": ("0.15.0", "0.19.0"),
|
|
20
|
+
"torchaudio": ("2.0.0", "2.4.0"),
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
CORE_DEPS = {
|
|
24
|
+
"numpy": ("1.24.0", None),
|
|
25
|
+
"requests": ("2.31.0", None),
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
OPTIONAL_DEPS = {
|
|
29
|
+
"coqui-tts": ("0.27.0", "0.30.0"),
|
|
30
|
+
"openai-whisper": ("20230314", None),
|
|
31
|
+
"sounddevice": ("0.4.6", None),
|
|
32
|
+
"librosa": ("0.10.0", "0.11.0"),
|
|
33
|
+
"flask": ("2.0.0", None),
|
|
34
|
+
"webrtcvad": ("2.0.10", None),
|
|
35
|
+
"PyAudio": ("0.2.13", None),
|
|
36
|
+
"soundfile": ("0.12.1", None),
|
|
37
|
+
"tiktoken": ("0.6.0", None),
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
def __init__(self, verbose: bool = True):
|
|
41
|
+
self.verbose = verbose
|
|
42
|
+
self.results = {}
|
|
43
|
+
|
|
44
|
+
def _parse_version(self, version_str: str) -> Tuple[int, ...]:
|
|
45
|
+
"""Parse version string into tuple of integers for comparison."""
|
|
46
|
+
try:
|
|
47
|
+
# Handle version strings like "20230314" or "2.0.0"
|
|
48
|
+
if version_str.isdigit():
|
|
49
|
+
return (int(version_str),)
|
|
50
|
+
|
|
51
|
+
# Standard semantic versioning
|
|
52
|
+
version_parts = version_str.split('.')
|
|
53
|
+
return tuple(int(part) for part in version_parts if part.isdigit())
|
|
54
|
+
except (ValueError, AttributeError):
|
|
55
|
+
return (0,)
|
|
56
|
+
|
|
57
|
+
def _check_version_range(self, current: str, min_ver: Optional[str], max_ver: Optional[str]) -> bool:
|
|
58
|
+
"""Check if current version is within the specified range."""
|
|
59
|
+
current_tuple = self._parse_version(current)
|
|
60
|
+
|
|
61
|
+
if min_ver:
|
|
62
|
+
min_tuple = self._parse_version(min_ver)
|
|
63
|
+
if current_tuple < min_tuple:
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
if max_ver:
|
|
67
|
+
max_tuple = self._parse_version(max_ver)
|
|
68
|
+
if current_tuple >= max_tuple:
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
return True
|
|
72
|
+
|
|
73
|
+
def _check_package(self, package_name: str, min_ver: Optional[str], max_ver: Optional[str]) -> Dict:
|
|
74
|
+
"""Check a single package installation and version."""
|
|
75
|
+
try:
|
|
76
|
+
module = importlib.import_module(package_name.replace('-', '_'))
|
|
77
|
+
version = getattr(module, '__version__', 'unknown')
|
|
78
|
+
|
|
79
|
+
# Special handling for packages with different version attributes
|
|
80
|
+
if version == 'unknown':
|
|
81
|
+
if hasattr(module, 'version'):
|
|
82
|
+
version = module.version
|
|
83
|
+
elif hasattr(module, 'VERSION'):
|
|
84
|
+
version = module.VERSION
|
|
85
|
+
elif package_name == 'openai-whisper':
|
|
86
|
+
# Whisper uses different versioning
|
|
87
|
+
try:
|
|
88
|
+
import whisper
|
|
89
|
+
version = getattr(whisper, '__version__', 'installed')
|
|
90
|
+
except:
|
|
91
|
+
version = 'installed'
|
|
92
|
+
|
|
93
|
+
compatible = self._check_version_range(str(version), min_ver, max_ver)
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
'status': 'installed',
|
|
97
|
+
'version': str(version),
|
|
98
|
+
'compatible': compatible,
|
|
99
|
+
'min_version': min_ver,
|
|
100
|
+
'max_version': max_ver
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
except ImportError:
|
|
104
|
+
return {
|
|
105
|
+
'status': 'missing',
|
|
106
|
+
'version': None,
|
|
107
|
+
'compatible': False,
|
|
108
|
+
'min_version': min_ver,
|
|
109
|
+
'max_version': max_ver
|
|
110
|
+
}
|
|
111
|
+
except Exception as e:
|
|
112
|
+
return {
|
|
113
|
+
'status': 'error',
|
|
114
|
+
'version': None,
|
|
115
|
+
'compatible': False,
|
|
116
|
+
'error': str(e),
|
|
117
|
+
'min_version': min_ver,
|
|
118
|
+
'max_version': max_ver
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
def check_core_dependencies(self) -> Dict:
|
|
122
|
+
"""Check core dependencies (always required)."""
|
|
123
|
+
results = {}
|
|
124
|
+
for package, (min_ver, max_ver) in self.CORE_DEPS.items():
|
|
125
|
+
results[package] = self._check_package(package, min_ver, max_ver)
|
|
126
|
+
return results
|
|
127
|
+
|
|
128
|
+
def check_pytorch_ecosystem(self) -> Dict:
|
|
129
|
+
"""Check PyTorch ecosystem for compatibility issues."""
|
|
130
|
+
results = {}
|
|
131
|
+
for package, (min_ver, max_ver) in self.PYTORCH_COMPAT.items():
|
|
132
|
+
results[package] = self._check_package(package, min_ver, max_ver)
|
|
133
|
+
return results
|
|
134
|
+
|
|
135
|
+
def check_optional_dependencies(self) -> Dict:
|
|
136
|
+
"""Check optional dependencies."""
|
|
137
|
+
results = {}
|
|
138
|
+
for package, (min_ver, max_ver) in self.OPTIONAL_DEPS.items():
|
|
139
|
+
results[package] = self._check_package(package, min_ver, max_ver)
|
|
140
|
+
return results
|
|
141
|
+
|
|
142
|
+
def check_pytorch_conflicts(self) -> List[str]:
|
|
143
|
+
"""Detect specific PyTorch/TorchVision conflicts."""
|
|
144
|
+
conflicts = []
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
import torch
|
|
148
|
+
import torchvision
|
|
149
|
+
|
|
150
|
+
torch_version = self._parse_version(torch.__version__)
|
|
151
|
+
tv_version = self._parse_version(torchvision.__version__)
|
|
152
|
+
|
|
153
|
+
# Check known incompatible combinations
|
|
154
|
+
if torch_version >= (2, 3, 0) and tv_version < (0, 18, 0):
|
|
155
|
+
conflicts.append(f"PyTorch {torch.__version__} is incompatible with TorchVision {torchvision.__version__}")
|
|
156
|
+
|
|
157
|
+
# Test torchvision::nms operator availability
|
|
158
|
+
try:
|
|
159
|
+
from torchvision.ops import nms
|
|
160
|
+
# Try to use nms to check if it actually works
|
|
161
|
+
import torch
|
|
162
|
+
boxes = torch.tensor([[0, 0, 1, 1]], dtype=torch.float32)
|
|
163
|
+
scores = torch.tensor([1.0], dtype=torch.float32)
|
|
164
|
+
nms(boxes, scores, 0.5)
|
|
165
|
+
except Exception as e:
|
|
166
|
+
if "torchvision::nms does not exist" in str(e):
|
|
167
|
+
conflicts.append("TorchVision NMS operator not available - version mismatch detected")
|
|
168
|
+
|
|
169
|
+
except ImportError:
|
|
170
|
+
pass # PyTorch not installed
|
|
171
|
+
|
|
172
|
+
return conflicts
|
|
173
|
+
|
|
174
|
+
def check_all(self) -> Dict:
|
|
175
|
+
"""Run comprehensive dependency check."""
|
|
176
|
+
results = {
|
|
177
|
+
'core': self.check_core_dependencies(),
|
|
178
|
+
'pytorch': self.check_pytorch_ecosystem(),
|
|
179
|
+
'optional': self.check_optional_dependencies(),
|
|
180
|
+
'conflicts': self.check_pytorch_conflicts(),
|
|
181
|
+
'python_version': sys.version,
|
|
182
|
+
'platform': sys.platform
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
self.results = results
|
|
186
|
+
return results
|
|
187
|
+
|
|
188
|
+
def print_report(self, results: Optional[Dict] = None):
|
|
189
|
+
"""Print a formatted dependency report."""
|
|
190
|
+
if results is None:
|
|
191
|
+
results = self.results
|
|
192
|
+
|
|
193
|
+
print("🔍 AbstractVoice Dependency Check Report")
|
|
194
|
+
print("=" * 50)
|
|
195
|
+
|
|
196
|
+
# Python version
|
|
197
|
+
print(f"\n🐍 Python: {results['python_version']}")
|
|
198
|
+
print(f"🖥️ Platform: {results['platform']}")
|
|
199
|
+
|
|
200
|
+
# Core dependencies
|
|
201
|
+
print(f"\n📦 Core Dependencies:")
|
|
202
|
+
for package, info in results['core'].items():
|
|
203
|
+
status_icon = "✅" if info['status'] == 'installed' and info['compatible'] else "❌"
|
|
204
|
+
version_info = f"v{info['version']}" if info['version'] else "not installed"
|
|
205
|
+
print(f" {status_icon} {package}: {version_info}")
|
|
206
|
+
|
|
207
|
+
# PyTorch ecosystem
|
|
208
|
+
print(f"\n🔥 PyTorch Ecosystem:")
|
|
209
|
+
pytorch_all_good = True
|
|
210
|
+
for package, info in results['pytorch'].items():
|
|
211
|
+
status_icon = "✅" if info['status'] == 'installed' and info['compatible'] else "❌"
|
|
212
|
+
version_info = f"v{info['version']}" if info['version'] else "not installed"
|
|
213
|
+
if info['status'] != 'installed' or not info['compatible']:
|
|
214
|
+
pytorch_all_good = False
|
|
215
|
+
print(f" {status_icon} {package}: {version_info}")
|
|
216
|
+
|
|
217
|
+
# Conflicts
|
|
218
|
+
if results['conflicts']:
|
|
219
|
+
print(f"\n⚠️ Detected Conflicts:")
|
|
220
|
+
for conflict in results['conflicts']:
|
|
221
|
+
print(f" ❌ {conflict}")
|
|
222
|
+
pytorch_all_good = False
|
|
223
|
+
|
|
224
|
+
if pytorch_all_good and all(info['status'] == 'installed' for info in results['pytorch'].values()):
|
|
225
|
+
print(" 🎉 PyTorch ecosystem looks compatible!")
|
|
226
|
+
|
|
227
|
+
# Optional dependencies
|
|
228
|
+
print(f"\n🔧 Optional Dependencies:")
|
|
229
|
+
installed_optional = []
|
|
230
|
+
missing_optional = []
|
|
231
|
+
|
|
232
|
+
for package, info in results['optional'].items():
|
|
233
|
+
if info['status'] == 'installed':
|
|
234
|
+
status_icon = "✅" if info['compatible'] else "⚠️"
|
|
235
|
+
installed_optional.append(f" {status_icon} {package}: v{info['version']}")
|
|
236
|
+
else:
|
|
237
|
+
missing_optional.append(f" ⭕ {package}: not installed")
|
|
238
|
+
|
|
239
|
+
if installed_optional:
|
|
240
|
+
print(" Installed:")
|
|
241
|
+
for line in installed_optional:
|
|
242
|
+
print(line)
|
|
243
|
+
|
|
244
|
+
if missing_optional:
|
|
245
|
+
print(" Missing:")
|
|
246
|
+
for line in missing_optional:
|
|
247
|
+
print(line)
|
|
248
|
+
|
|
249
|
+
# Recommendations
|
|
250
|
+
print(f"\n💡 Recommendations:")
|
|
251
|
+
|
|
252
|
+
if not pytorch_all_good:
|
|
253
|
+
print(" 🔧 Fix PyTorch conflicts with:")
|
|
254
|
+
print(" pip uninstall torch torchvision torchaudio")
|
|
255
|
+
print(" pip install abstractvoice[all]")
|
|
256
|
+
|
|
257
|
+
if not any(info['status'] == 'installed' for info in results['optional'].values()):
|
|
258
|
+
print(" 📦 Install voice functionality with:")
|
|
259
|
+
print(" pip install abstractvoice[voice-full]")
|
|
260
|
+
|
|
261
|
+
print("\n" + "=" * 50)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def check_dependencies(verbose: bool = True) -> Dict:
|
|
265
|
+
"""Quick function to check all dependencies."""
|
|
266
|
+
checker = DependencyChecker(verbose=verbose)
|
|
267
|
+
results = checker.check_all()
|
|
268
|
+
if verbose:
|
|
269
|
+
checker.print_report(results)
|
|
270
|
+
return results
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
if __name__ == "__main__":
|
|
274
|
+
check_dependencies()
|
|
@@ -37,21 +37,28 @@ class VoiceREPL(cmd.Cmd):
|
|
|
37
37
|
ruler = "" # No horizontal rule line
|
|
38
38
|
use_rawinput = True
|
|
39
39
|
|
|
40
|
-
def __init__(self, api_url="http://localhost:11434/api/chat",
|
|
41
|
-
model="granite3.3:2b", debug_mode=False):
|
|
40
|
+
def __init__(self, api_url="http://localhost:11434/api/chat",
|
|
41
|
+
model="granite3.3:2b", debug_mode=False, language="en", tts_model=None):
|
|
42
42
|
super().__init__()
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
# Debug mode
|
|
45
45
|
self.debug_mode = debug_mode
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
# API settings
|
|
48
48
|
self.api_url = api_url
|
|
49
49
|
self.model = model
|
|
50
50
|
self.temperature = 0.4
|
|
51
51
|
self.max_tokens = 4096
|
|
52
|
-
|
|
53
|
-
#
|
|
54
|
-
self.
|
|
52
|
+
|
|
53
|
+
# Language settings
|
|
54
|
+
self.current_language = language
|
|
55
|
+
|
|
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
|
+
)
|
|
55
62
|
|
|
56
63
|
# Settings
|
|
57
64
|
self.use_tts = True
|
|
@@ -83,10 +90,12 @@ class VoiceREPL(cmd.Cmd):
|
|
|
83
90
|
def _get_intro(self):
|
|
84
91
|
"""Generate intro message with help."""
|
|
85
92
|
intro = f"\n{Colors.BOLD}Welcome to AbstractVoice CLI REPL{Colors.END}\n"
|
|
86
|
-
|
|
93
|
+
lang_name = self.voice_manager.get_language_name()
|
|
94
|
+
intro += f"API: {self.api_url} | Model: {self.model} | Voice: {lang_name}\n"
|
|
87
95
|
intro += f"\n{Colors.CYAN}Quick Start:{Colors.END}\n"
|
|
88
96
|
intro += " • Type messages to chat with the LLM\n"
|
|
89
97
|
intro += " • Use /voice <mode> to enable voice input\n"
|
|
98
|
+
intro += " • Use /language <lang> to switch voice language\n"
|
|
90
99
|
intro += " • Type /help for full command list\n"
|
|
91
100
|
intro += " • Type /exit or /q to quit\n"
|
|
92
101
|
return intro
|
|
@@ -278,7 +287,172 @@ class VoiceREPL(cmd.Cmd):
|
|
|
278
287
|
text = re.sub(pattern, "", text, flags=re.DOTALL)
|
|
279
288
|
|
|
280
289
|
return text.strip()
|
|
281
|
-
|
|
290
|
+
|
|
291
|
+
def do_language(self, args):
|
|
292
|
+
"""Switch voice language.
|
|
293
|
+
|
|
294
|
+
Usage: /language <lang>
|
|
295
|
+
Available languages: en, fr, es, de, it
|
|
296
|
+
"""
|
|
297
|
+
if not args:
|
|
298
|
+
current_name = self.voice_manager.get_language_name()
|
|
299
|
+
current_code = self.voice_manager.get_language()
|
|
300
|
+
print(f"Current language: {current_name} ({current_code})")
|
|
301
|
+
|
|
302
|
+
print("Available languages:")
|
|
303
|
+
for code in self.voice_manager.get_supported_languages():
|
|
304
|
+
name = self.voice_manager.get_language_name(code)
|
|
305
|
+
print(f" {code} - {name}")
|
|
306
|
+
return
|
|
307
|
+
|
|
308
|
+
language = args.strip().lower()
|
|
309
|
+
|
|
310
|
+
# Stop any current voice activity
|
|
311
|
+
if self.voice_mode_active:
|
|
312
|
+
self._voice_stop_callback()
|
|
313
|
+
was_active = True
|
|
314
|
+
else:
|
|
315
|
+
was_active = False
|
|
316
|
+
|
|
317
|
+
# Switch language
|
|
318
|
+
old_lang = self.current_language
|
|
319
|
+
if self.voice_manager.set_language(language):
|
|
320
|
+
self.current_language = language
|
|
321
|
+
old_name = self.voice_manager.get_language_name(old_lang)
|
|
322
|
+
new_name = self.voice_manager.get_language_name(language)
|
|
323
|
+
print(f"🌍 Language changed: {old_name} → {new_name}")
|
|
324
|
+
|
|
325
|
+
# Test the new language with localized message
|
|
326
|
+
test_messages = {
|
|
327
|
+
'en': "Language switched to English.",
|
|
328
|
+
'fr': "Langue changée en français.",
|
|
329
|
+
'es': "Idioma cambiado a español.",
|
|
330
|
+
'de': "Sprache auf Deutsch umgestellt.",
|
|
331
|
+
'it': "Lingua cambiata in italiano."
|
|
332
|
+
}
|
|
333
|
+
test_msg = test_messages.get(language, "Language switched.")
|
|
334
|
+
self.voice_manager.speak(test_msg)
|
|
335
|
+
|
|
336
|
+
# Restart voice mode if it was active
|
|
337
|
+
if was_active:
|
|
338
|
+
self.do_voice(self.voice_mode)
|
|
339
|
+
else:
|
|
340
|
+
supported = ', '.join(self.voice_manager.get_supported_languages())
|
|
341
|
+
print(f"Failed to switch to language: {language}")
|
|
342
|
+
print(f"Supported languages: {supported}")
|
|
343
|
+
if self.debug_mode:
|
|
344
|
+
import traceback
|
|
345
|
+
traceback.print_exc()
|
|
346
|
+
|
|
347
|
+
def do_setvoice(self, args):
|
|
348
|
+
"""Set a specific voice model.
|
|
349
|
+
|
|
350
|
+
Usage:
|
|
351
|
+
/setvoice # Show all available voices
|
|
352
|
+
/setvoice <voice_id> # Set voice (format: language.voice_id)
|
|
353
|
+
|
|
354
|
+
Examples:
|
|
355
|
+
/setvoice # List all voices
|
|
356
|
+
/setvoice fr.css10_vits # Set French CSS10 VITS voice
|
|
357
|
+
/setvoice it.mai_male_vits # Set Italian male VITS voice
|
|
358
|
+
"""
|
|
359
|
+
if not args:
|
|
360
|
+
# Show all available voices organized by language
|
|
361
|
+
print(f"\n{Colors.CYAN}Available Voice Models:{Colors.END}")
|
|
362
|
+
self.voice_manager.list_voices()
|
|
363
|
+
|
|
364
|
+
print(f"\n{Colors.YELLOW}Usage:{Colors.END}")
|
|
365
|
+
print(" /setvoice <language>.<voice_id>")
|
|
366
|
+
print(" Example: /setvoice fr.css10_vits")
|
|
367
|
+
return
|
|
368
|
+
|
|
369
|
+
voice_spec = args.strip()
|
|
370
|
+
|
|
371
|
+
# Parse language.voice_id format
|
|
372
|
+
if '.' not in voice_spec:
|
|
373
|
+
print(f"❌ Invalid format. Use: language.voice_id")
|
|
374
|
+
print(f" Example: /setvoice fr.css10_vits")
|
|
375
|
+
print(f" Run '/setvoice' to see available voices")
|
|
376
|
+
return
|
|
377
|
+
|
|
378
|
+
try:
|
|
379
|
+
language, voice_id = voice_spec.split('.', 1)
|
|
380
|
+
except ValueError:
|
|
381
|
+
print(f"❌ Invalid format. Use: language.voice_id")
|
|
382
|
+
return
|
|
383
|
+
|
|
384
|
+
# Stop any current voice activity
|
|
385
|
+
if self.voice_mode_active:
|
|
386
|
+
self._voice_stop_callback()
|
|
387
|
+
was_active = True
|
|
388
|
+
else:
|
|
389
|
+
was_active = False
|
|
390
|
+
|
|
391
|
+
# Set the specific voice
|
|
392
|
+
try:
|
|
393
|
+
success = self.voice_manager.set_voice(language, voice_id)
|
|
394
|
+
if success:
|
|
395
|
+
# Update current language to match the voice
|
|
396
|
+
self.current_language = language
|
|
397
|
+
|
|
398
|
+
# Get voice info for confirmation
|
|
399
|
+
voice_info = self.voice_manager.VOICE_CATALOG.get(language, {}).get(voice_id, {})
|
|
400
|
+
lang_name = self.voice_manager.get_language_name(language)
|
|
401
|
+
|
|
402
|
+
print(f"✅ Voice changed successfully!")
|
|
403
|
+
print(f" Language: {lang_name} ({language})")
|
|
404
|
+
print(f" Voice: {voice_id}")
|
|
405
|
+
if voice_info:
|
|
406
|
+
quality_icon = "✨" if voice_info.get('quality') == 'premium' else "🔧"
|
|
407
|
+
gender_icon = {"male": "👨", "female": "👩", "multiple": "👥"}.get(voice_info.get('gender'), "🗣️")
|
|
408
|
+
print(f" Details: {quality_icon} {gender_icon} {voice_info.get('accent', 'Unknown accent')}")
|
|
409
|
+
|
|
410
|
+
# Test the new voice
|
|
411
|
+
test_messages = {
|
|
412
|
+
'en': "Voice changed to English.",
|
|
413
|
+
'fr': "Voix changée en français.",
|
|
414
|
+
'es': "Voz cambiada al español.",
|
|
415
|
+
'de': "Stimme auf Deutsch geändert.",
|
|
416
|
+
'it': "Voce cambiata in italiano."
|
|
417
|
+
}
|
|
418
|
+
test_msg = test_messages.get(language, "Voice changed successfully.")
|
|
419
|
+
self.voice_manager.speak(test_msg)
|
|
420
|
+
|
|
421
|
+
# Restart voice mode if it was active
|
|
422
|
+
if was_active:
|
|
423
|
+
self.do_voice(self.voice_mode)
|
|
424
|
+
else:
|
|
425
|
+
print(f"❌ Failed to set voice: {voice_spec}")
|
|
426
|
+
print(f" Run '/setvoice' to see available voices")
|
|
427
|
+
|
|
428
|
+
except Exception as e:
|
|
429
|
+
print(f"❌ Error setting voice: {e}")
|
|
430
|
+
if self.debug_mode:
|
|
431
|
+
import traceback
|
|
432
|
+
traceback.print_exc()
|
|
433
|
+
|
|
434
|
+
def do_lang_info(self, args):
|
|
435
|
+
"""Show current language information."""
|
|
436
|
+
info = self.voice_manager.get_language_info()
|
|
437
|
+
print(f"\n{Colors.CYAN}Current Language Information:{Colors.END}")
|
|
438
|
+
print(f" Language: {info['name']} ({info['code']})")
|
|
439
|
+
print(f" Model: {info['model']}")
|
|
440
|
+
print(f" Available models: {list(info['available_models'].keys())}")
|
|
441
|
+
|
|
442
|
+
# Check if XTTS supports multiple languages
|
|
443
|
+
if 'xtts' in (info['model'] or '').lower():
|
|
444
|
+
print(f" ✅ Supports multilingual synthesis")
|
|
445
|
+
else:
|
|
446
|
+
print(f" ℹ️ Monolingual model")
|
|
447
|
+
|
|
448
|
+
def do_list_languages(self, args):
|
|
449
|
+
"""List all supported languages."""
|
|
450
|
+
print(f"\n{Colors.CYAN}Supported Languages:{Colors.END}")
|
|
451
|
+
for lang in self.voice_manager.get_supported_languages():
|
|
452
|
+
name = self.voice_manager.get_language_name(lang)
|
|
453
|
+
current = " (current)" if lang == self.current_language else ""
|
|
454
|
+
print(f" {lang} - {name}{current}")
|
|
455
|
+
|
|
282
456
|
def do_voice(self, arg):
|
|
283
457
|
"""Control voice input mode.
|
|
284
458
|
|
|
@@ -554,6 +728,10 @@ class VoiceREPL(cmd.Cmd):
|
|
|
554
728
|
print(" /clear Clear history")
|
|
555
729
|
print(" /tts on|off Toggle TTS")
|
|
556
730
|
print(" /voice <mode> Voice input: off|full|wait|stop|ptt")
|
|
731
|
+
print(" /language <lang> Switch voice language (en, fr, es, de, it)")
|
|
732
|
+
print(" /setvoice [id] List voices or set specific voice (lang.voice_id)")
|
|
733
|
+
print(" /lang_info Show current language information")
|
|
734
|
+
print(" /list_languages List all supported languages")
|
|
557
735
|
print(" /speed <number> Set TTS speed (0.5-2.0, default: 1.0, pitch preserved)")
|
|
558
736
|
print(" /tts_model <model> Switch TTS model: vits(best)|fast_pitch|glow-tts|tacotron2-DDC")
|
|
559
737
|
print(" /whisper <model> Switch Whisper model: tiny|base|small|medium|large")
|
|
@@ -831,10 +1009,15 @@ def parse_args():
|
|
|
831
1009
|
"""Parse command line arguments."""
|
|
832
1010
|
parser = argparse.ArgumentParser(description="AbstractVoice CLI Example")
|
|
833
1011
|
parser.add_argument("--debug", action="store_true", help="Enable debug mode")
|
|
834
|
-
parser.add_argument("--api", default="http://localhost:11434/api/chat",
|
|
1012
|
+
parser.add_argument("--api", default="http://localhost:11434/api/chat",
|
|
835
1013
|
help="LLM API URL")
|
|
836
|
-
parser.add_argument("--model", default="granite3.3:2b",
|
|
1014
|
+
parser.add_argument("--model", default="granite3.3:2b",
|
|
837
1015
|
help="LLM model name")
|
|
1016
|
+
parser.add_argument("--language", "--lang", default="en",
|
|
1017
|
+
choices=["en", "fr", "es", "de", "it", "ru", "multilingual"],
|
|
1018
|
+
help="Voice language (en=English, fr=French, es=Spanish, de=German, it=Italian, ru=Russian, multilingual=All)")
|
|
1019
|
+
parser.add_argument("--tts-model",
|
|
1020
|
+
help="Specific TTS model to use (overrides language default)")
|
|
838
1021
|
return parser.parse_args()
|
|
839
1022
|
|
|
840
1023
|
|
|
@@ -844,11 +1027,13 @@ def main():
|
|
|
844
1027
|
# Parse command line arguments
|
|
845
1028
|
args = parse_args()
|
|
846
1029
|
|
|
847
|
-
# Initialize and run REPL
|
|
1030
|
+
# Initialize and run REPL with language support
|
|
848
1031
|
repl = VoiceREPL(
|
|
849
1032
|
api_url=args.api,
|
|
850
1033
|
model=args.model,
|
|
851
|
-
debug_mode=args.debug
|
|
1034
|
+
debug_mode=args.debug,
|
|
1035
|
+
language=args.language,
|
|
1036
|
+
tts_model=args.tts_model
|
|
852
1037
|
)
|
|
853
1038
|
repl.cmdloop()
|
|
854
1039
|
except KeyboardInterrupt:
|
|
@@ -13,20 +13,25 @@ def parse_args():
|
|
|
13
13
|
"""Parse command line arguments."""
|
|
14
14
|
parser = argparse.ArgumentParser(description="AbstractVoice Voice Mode")
|
|
15
15
|
parser.add_argument("--debug", action="store_true", help="Enable debug mode")
|
|
16
|
-
parser.add_argument("--api", default="http://localhost:11434/api/chat",
|
|
16
|
+
parser.add_argument("--api", default="http://localhost:11434/api/chat",
|
|
17
17
|
help="LLM API URL")
|
|
18
|
-
parser.add_argument("--model", default="granite3.3:2b",
|
|
18
|
+
parser.add_argument("--model", default="granite3.3:2b",
|
|
19
19
|
help="LLM model name")
|
|
20
20
|
parser.add_argument("--whisper", default="tiny",
|
|
21
21
|
help="Whisper model to use (tiny, base, small, medium, large)")
|
|
22
22
|
parser.add_argument("--no-listening", action="store_true",
|
|
23
23
|
help="Disable speech-to-text (listening), TTS still works")
|
|
24
|
-
parser.add_argument("--system",
|
|
24
|
+
parser.add_argument("--system",
|
|
25
25
|
help="Custom system prompt")
|
|
26
26
|
parser.add_argument("--temperature", type=float, default=0.4,
|
|
27
27
|
help="Set temperature (0.0-2.0) for the LLM")
|
|
28
28
|
parser.add_argument("--max-tokens", type=int, default=4096,
|
|
29
29
|
help="Set maximum tokens for the LLM response")
|
|
30
|
+
parser.add_argument("--language", "--lang", default="en",
|
|
31
|
+
choices=["en", "fr", "es", "de", "it", "ru", "multilingual"],
|
|
32
|
+
help="Voice language (en=English, fr=French, es=Spanish, de=German, it=Italian, ru=Russian, multilingual=All)")
|
|
33
|
+
parser.add_argument("--tts-model",
|
|
34
|
+
help="Specific TTS model to use (overrides language default)")
|
|
30
35
|
return parser.parse_args()
|
|
31
36
|
|
|
32
37
|
def main():
|
|
@@ -35,13 +40,22 @@ def main():
|
|
|
35
40
|
# Parse command line arguments
|
|
36
41
|
args = parse_args()
|
|
37
42
|
|
|
38
|
-
|
|
43
|
+
# Show language information
|
|
44
|
+
language_names = {
|
|
45
|
+
'en': 'English', 'fr': 'French', 'es': 'Spanish',
|
|
46
|
+
'de': 'German', 'it': 'Italian', 'ru': 'Russian',
|
|
47
|
+
'multilingual': 'Multilingual'
|
|
48
|
+
}
|
|
49
|
+
lang_name = language_names.get(args.language, args.language)
|
|
50
|
+
print(f"Starting AbstractVoice voice interface ({lang_name})...")
|
|
39
51
|
|
|
40
|
-
# Initialize REPL
|
|
52
|
+
# Initialize REPL with language support
|
|
41
53
|
repl = VoiceREPL(
|
|
42
54
|
api_url=args.api,
|
|
43
55
|
model=args.model,
|
|
44
|
-
debug_mode=args.debug
|
|
56
|
+
debug_mode=args.debug,
|
|
57
|
+
language=args.language,
|
|
58
|
+
tts_model=args.tts_model
|
|
45
59
|
)
|
|
46
60
|
|
|
47
61
|
# Set custom system prompt if provided
|