agentvibes 5.9.0 → 5.10.1
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.
- package/.agentvibes/config.json +3 -12
- package/.claude/commands/agent-vibes-bmad-voices.md +117 -117
- package/.claude/commands/agent-vibes-rdp.md +24 -24
- package/.claude/config/audio-effects.cfg +4 -5
- package/.claude/config/audio-effects.cfg.sample +52 -52
- package/.claude/config/background-music-enabled.txt +1 -1
- package/.claude/docs/TERMUX_SETUP.md +408 -408
- package/.claude/github-star-reminder.txt +1 -1
- package/.claude/hooks/audio-cache-utils.sh +0 -0
- package/.claude/hooks/audio-processor.sh +0 -0
- package/.claude/hooks/background-music-manager.sh +0 -0
- package/.claude/hooks/bmad-party-speak.sh +0 -0
- package/.claude/hooks/bmad-speak-enhanced.sh +0 -0
- package/.claude/hooks/bmad-speak.sh +0 -0
- package/.claude/hooks/bmad-tts-injector.sh +0 -0
- package/.claude/hooks/bmad-voice-manager.sh +0 -0
- package/.claude/hooks/clawdbot-receiver-SECURE.sh +0 -0
- package/.claude/hooks/clawdbot-receiver.sh +0 -0
- package/.claude/hooks/clean-audio-cache.sh +0 -0
- package/.claude/hooks/cleanup-cache.sh +0 -0
- package/.claude/hooks/configure-rdp-mode.sh +0 -0
- package/.claude/hooks/download-extra-voices.sh +0 -0
- package/.claude/hooks/effects-manager.sh +0 -0
- package/.claude/hooks/github-star-reminder.sh +0 -0
- package/.claude/hooks/language-manager.sh +0 -0
- package/.claude/hooks/learn-manager.sh +0 -0
- package/.claude/hooks/macos-voice-manager.sh +0 -0
- package/.claude/hooks/migrate-background-music.sh +0 -0
- package/.claude/hooks/migrate-to-agentvibes.sh +0 -0
- package/.claude/hooks/optimize-background-music.sh +0 -0
- package/.claude/hooks/path-resolver.sh +0 -0
- package/.claude/hooks/personality-manager.sh +0 -0
- package/.claude/hooks/piper-download-voices.sh +0 -0
- package/.claude/hooks/piper-installer.sh +0 -0
- package/.claude/hooks/piper-multispeaker-registry.sh +0 -0
- package/.claude/hooks/piper-voice-manager.sh +0 -0
- package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +0 -0
- package/.claude/hooks/play-tts-enhanced.sh +0 -0
- package/.claude/hooks/play-tts-macos.sh +0 -0
- package/.claude/hooks/play-tts-piper.sh +20 -13
- package/.claude/hooks/play-tts-soprano.sh +0 -0
- package/.claude/hooks/play-tts-ssh-remote.sh +0 -0
- package/.claude/hooks/play-tts-termux-ssh.sh +0 -0
- package/.claude/hooks/play-tts-windows-receiver.sh +0 -0
- package/.claude/hooks/play-tts.sh +0 -0
- package/.claude/hooks/prepare-release.sh +0 -0
- package/.claude/hooks/provider-commands.sh +0 -0
- package/.claude/hooks/provider-manager.sh +0 -0
- package/.claude/hooks/replay-target-audio.sh +0 -0
- package/.claude/hooks/requirements.txt +6 -6
- package/.claude/hooks/sentiment-manager.sh +0 -0
- package/.claude/hooks/session-start-tts.sh +0 -0
- package/.claude/hooks/soprano-gradio-synth.py +139 -139
- package/.claude/hooks/speed-manager.sh +0 -0
- package/.claude/hooks/stop-tts.sh +0 -0
- package/.claude/hooks/termux-installer.sh +0 -0
- package/.claude/hooks/translate-manager.sh +0 -0
- package/.claude/hooks/translator.py +237 -237
- package/.claude/hooks/tts-queue-worker.sh +0 -0
- package/.claude/hooks/tts-queue.sh +0 -0
- package/.claude/hooks/verbosity-manager.sh +0 -0
- package/.claude/hooks/voice-manager.sh +6 -0
- package/.claude/hooks-windows/play-tts-windows-piper.ps1 +22 -16
- package/.claude/hooks-windows/soprano-gradio-synth.py +153 -153
- package/.claude/verbosity.txt +1 -1
- package/.clawdbot/README.md +105 -105
- package/.mcp.json +19 -6
- package/README.md +1 -1
- package/WINDOWS-SETUP.md +208 -208
- package/bin/agent-vibes +39 -39
- package/bin/agentvibes-voice-browser.js +0 -0
- package/bin/agentvibes.js +0 -0
- package/bin/mcp-server.js +121 -121
- package/bin/mcp-server.sh +0 -0
- package/bin/test-bmad-pr +78 -78
- package/mcp-server/QUICK_START.md +203 -203
- package/mcp-server/README.md +345 -345
- package/mcp-server/WINDOWS_SETUP.md +0 -0
- package/mcp-server/examples/claude_desktop_config.json +11 -11
- package/mcp-server/examples/claude_desktop_config_piper.json +9 -9
- package/mcp-server/examples/custom_instructions.md +169 -169
- package/mcp-server/install-deps.js +0 -0
- package/mcp-server/server.py +1807 -1797
- package/mcp-server/test_server.py +0 -0
- package/package.json +2 -2
- package/src/cli/list-personalities.js +110 -110
- package/src/cli/list-voices.js +114 -114
- package/src/commands/bmad-voices.js +394 -394
- package/src/commands/install-mcp.js +730 -476
- package/src/console/app.js +3 -3
- package/src/console/brand-colors.js +13 -13
- package/src/console/constants/personalities.js +44 -44
- package/src/console/tabs/agents-tab.js +6 -6
- package/src/console/tabs/help-tab.js +314 -314
- package/src/console/tabs/music-tab.js +1 -1
- package/src/console/tabs/readme-tab.js +272 -272
- package/src/console/tabs/receiver-tab.js +13 -13
- package/src/console/tabs/settings-tab.js +2 -2
- package/src/console/tabs/setup-tab.js +10 -10
- package/src/console/tabs/voices-tab.js +4 -4
- package/src/console/widgets/destroy-list.js +25 -25
- package/src/console/widgets/notice.js +55 -55
- package/src/console/widgets/personality-picker.js +2 -2
- package/src/console/widgets/reverb-picker.js +1 -1
- package/src/i18n/de.js +202 -202
- package/src/i18n/es.js +202 -202
- package/src/i18n/fr.js +202 -202
- package/src/i18n/hi.js +202 -202
- package/src/i18n/ja.js +202 -202
- package/src/i18n/ko.js +202 -202
- package/src/i18n/pt.js +202 -202
- package/src/i18n/strings.js +54 -54
- package/src/i18n/zh-CN.js +202 -202
- package/src/installer/language-screen.js +31 -31
- package/src/installer/music-file-input.js +304 -304
- package/src/installer.js +32 -27
- package/src/services/config-service.js +264 -264
- package/src/services/language-service.js +47 -47
- package/src/services/provider-service.js +143 -143
- package/src/services/tts-engine-service.js +2 -2
- package/src/utils/audio-duration-validator.js +298 -298
- package/src/utils/audio-format-validator.js +277 -277
- package/src/utils/dependency-checker.js +469 -469
- package/src/utils/file-ownership-verifier.js +358 -358
- package/src/utils/list-formatter.js +200 -194
- package/src/utils/music-file-validator.js +285 -285
- package/src/utils/platform-resolver.js +369 -0
- package/src/utils/preview-list-prompt.js +136 -136
- package/src/utils/provider-validator.js +9 -9
- package/src/utils/secure-music-storage.js +412 -412
- package/templates/agentvibes-receiver.sh +231 -231
- package/templates/audio/welcome-music.mp3 +0 -0
- package/.agentvibes/install-manifest.json +0 -330
- package/.claude/config/background-music-position.txt +0 -27
- package/.claude/config/background-music-volume.txt +0 -1
- package/.claude/config/background-music.cfg +0 -1
- package/.claude/config/background-music.txt +0 -1
- package/.claude/config/language.txt +0 -1
- package/.claude/config/reverb-level.txt +0 -1
- package/.claude/config/tts-speech-rate.txt +0 -1
- package/.claude/config/tts-verbosity.txt +0 -1
- package/.claude/hooks/play-tts-agentvibes-receiver.sh +0 -1
- package/.claude/hooks-windows/audio-cache-utils.ps1.user.bak +0 -119
- package/.claude/hooks-windows/soprano-gradio-synth.py.user.bak +0 -153
- package/.claude/piper-voices-dir.txt +0 -1
|
@@ -1,237 +1,237 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
#
|
|
3
|
-
# File: .claude/hooks/translator.py
|
|
4
|
-
#
|
|
5
|
-
# AgentVibes - Finally, your AI Agents can Talk Back! Text-to-Speech WITH personality for AI Assistants!
|
|
6
|
-
# Website: https://agentvibes.org
|
|
7
|
-
# Repository: https://github.com/paulpreibisch/AgentVibes
|
|
8
|
-
#
|
|
9
|
-
# Co-created by Paul Preibisch with Claude AI
|
|
10
|
-
# Copyright (c) 2025 Paul Preibisch
|
|
11
|
-
#
|
|
12
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
13
|
-
# you may not use this file except in compliance with the License.
|
|
14
|
-
# You may obtain a copy of the License at
|
|
15
|
-
#
|
|
16
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
17
|
-
#
|
|
18
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
19
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
20
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21
|
-
# See the License for the specific language governing permissions and
|
|
22
|
-
# limitations under the License.
|
|
23
|
-
#
|
|
24
|
-
# DISCLAIMER: This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
25
|
-
# express or implied. Use at your own risk. See the Apache License for details.
|
|
26
|
-
#
|
|
27
|
-
# ---
|
|
28
|
-
#
|
|
29
|
-
# @fileoverview Text translator for multi-language TTS and learning mode
|
|
30
|
-
# @context Provides automatic translation using Google Translate via deep-translator library
|
|
31
|
-
# @architecture Standalone CLI module callable from bash scripts, with library mode for Python imports
|
|
32
|
-
# @dependencies deep-translator, langdetect (pip install deep-translator langdetect)
|
|
33
|
-
# @entrypoints CLI: python3 translator.py <text> <target_lang>, Library: from translator import translate
|
|
34
|
-
# @patterns Command pattern - supports translate, detect, and batch operations
|
|
35
|
-
# @related play-tts.sh, learn-manager.sh, language-manager.sh
|
|
36
|
-
#
|
|
37
|
-
|
|
38
|
-
"""
|
|
39
|
-
Text translation utilities for AgentVibes multi-language TTS.
|
|
40
|
-
|
|
41
|
-
Provides automatic translation of TTS text to the user's preferred language,
|
|
42
|
-
supporting both BMAD communication_language settings and learning mode.
|
|
43
|
-
|
|
44
|
-
Usage:
|
|
45
|
-
CLI: python3 translator.py <text> <target_language>
|
|
46
|
-
Library: from translator import translate, detect_language
|
|
47
|
-
"""
|
|
48
|
-
|
|
49
|
-
import sys
|
|
50
|
-
import os
|
|
51
|
-
from typing import Optional, Tuple
|
|
52
|
-
|
|
53
|
-
# Language name to ISO code mapping
|
|
54
|
-
LANG_CODES = {
|
|
55
|
-
'spanish': 'es', 'español': 'es', 'es': 'es',
|
|
56
|
-
'french': 'fr', 'français': 'fr', 'fr': 'fr',
|
|
57
|
-
'german': 'de', 'deutsch': 'de', 'de': 'de',
|
|
58
|
-
'italian': 'it', 'italiano': 'it', 'it': 'it',
|
|
59
|
-
'portuguese': 'pt', 'português': 'pt', 'pt': 'pt',
|
|
60
|
-
'chinese': 'zh-CN', 'mandarin': 'zh-CN', 'zh': 'zh-CN', '中文': 'zh-CN',
|
|
61
|
-
'japanese': 'ja', '日本語': 'ja', 'ja': 'ja',
|
|
62
|
-
'korean': 'ko', '한국어': 'ko', 'ko': 'ko',
|
|
63
|
-
'russian': 'ru', 'русский': 'ru', 'ru': 'ru',
|
|
64
|
-
'polish': 'pl', 'polski': 'pl', 'pl': 'pl',
|
|
65
|
-
'dutch': 'nl', 'nederlands': 'nl', 'nl': 'nl',
|
|
66
|
-
'turkish': 'tr', 'türkçe': 'tr', 'tr': 'tr',
|
|
67
|
-
'arabic': 'ar', 'العربية': 'ar', 'ar': 'ar',
|
|
68
|
-
'hindi': 'hi', 'हिन्दी': 'hi', 'hi': 'hi',
|
|
69
|
-
'swedish': 'sv', 'svenska': 'sv', 'sv': 'sv',
|
|
70
|
-
'danish': 'da', 'dansk': 'da', 'da': 'da',
|
|
71
|
-
'norwegian': 'no', 'norsk': 'no', 'no': 'no',
|
|
72
|
-
'finnish': 'fi', 'suomi': 'fi', 'fi': 'fi',
|
|
73
|
-
'czech': 'cs', 'čeština': 'cs', 'cs': 'cs',
|
|
74
|
-
'romanian': 'ro', 'română': 'ro', 'ro': 'ro',
|
|
75
|
-
'ukrainian': 'uk', 'українська': 'uk', 'uk': 'uk',
|
|
76
|
-
'greek': 'el', 'ελληνικά': 'el', 'el': 'el',
|
|
77
|
-
'bulgarian': 'bg', 'български': 'bg', 'bg': 'bg',
|
|
78
|
-
'croatian': 'hr', 'hrvatski': 'hr', 'hr': 'hr',
|
|
79
|
-
'slovak': 'sk', 'slovenčina': 'sk', 'sk': 'sk',
|
|
80
|
-
'english': 'en', 'en': 'en',
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def get_lang_code(language: str) -> str:
|
|
85
|
-
"""
|
|
86
|
-
Convert language name to ISO code.
|
|
87
|
-
|
|
88
|
-
Args:
|
|
89
|
-
language: Language name or code (e.g., 'spanish', 'es', 'español')
|
|
90
|
-
|
|
91
|
-
Returns:
|
|
92
|
-
ISO language code (e.g., 'es')
|
|
93
|
-
"""
|
|
94
|
-
lang_lower = language.lower().strip()
|
|
95
|
-
return LANG_CODES.get(lang_lower, lang_lower)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def detect_language(text: str) -> Optional[str]:
|
|
99
|
-
"""
|
|
100
|
-
Detect the language of given text.
|
|
101
|
-
|
|
102
|
-
Args:
|
|
103
|
-
text: Text to analyze
|
|
104
|
-
|
|
105
|
-
Returns:
|
|
106
|
-
Language code (e.g., 'es', 'fr', 'en') or None if detection fails
|
|
107
|
-
"""
|
|
108
|
-
if not text or len(text.strip()) < 3:
|
|
109
|
-
return None
|
|
110
|
-
|
|
111
|
-
try:
|
|
112
|
-
from langdetect import detect, LangDetectException
|
|
113
|
-
return detect(text)
|
|
114
|
-
except ImportError:
|
|
115
|
-
print("Warning: langdetect not installed. Run: pip install langdetect", file=sys.stderr)
|
|
116
|
-
return None
|
|
117
|
-
except Exception:
|
|
118
|
-
return None
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def translate(text: str, target_lang: str, source_lang: str = 'en') -> Tuple[str, bool]:
|
|
122
|
-
"""
|
|
123
|
-
Translate text to target language.
|
|
124
|
-
|
|
125
|
-
Args:
|
|
126
|
-
text: Text to translate
|
|
127
|
-
target_lang: Target language (name or code)
|
|
128
|
-
source_lang: Source language (default: 'en')
|
|
129
|
-
|
|
130
|
-
Returns:
|
|
131
|
-
Tuple of (translated_text, success)
|
|
132
|
-
"""
|
|
133
|
-
if not text or not text.strip():
|
|
134
|
-
return text, False
|
|
135
|
-
|
|
136
|
-
# Convert language names to codes
|
|
137
|
-
target_code = get_lang_code(target_lang)
|
|
138
|
-
source_code = get_lang_code(source_lang)
|
|
139
|
-
|
|
140
|
-
# Skip if source and target are the same
|
|
141
|
-
if target_code == source_code:
|
|
142
|
-
return text, False
|
|
143
|
-
|
|
144
|
-
# Skip if target is English and source is also English
|
|
145
|
-
if target_code == 'en' and source_code == 'en':
|
|
146
|
-
return text, False
|
|
147
|
-
|
|
148
|
-
try:
|
|
149
|
-
from deep_translator import GoogleTranslator
|
|
150
|
-
|
|
151
|
-
translator = GoogleTranslator(source=source_code, target=target_code)
|
|
152
|
-
translated = translator.translate(text)
|
|
153
|
-
|
|
154
|
-
if translated:
|
|
155
|
-
return translated, True
|
|
156
|
-
return text, False
|
|
157
|
-
|
|
158
|
-
except ImportError:
|
|
159
|
-
print("Error: deep-translator not installed. Run: pip install deep-translator", file=sys.stderr)
|
|
160
|
-
return text, False
|
|
161
|
-
except Exception as e:
|
|
162
|
-
print(f"Translation error: {e}", file=sys.stderr)
|
|
163
|
-
return text, False
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
def translate_auto(text: str, target_lang: str) -> Tuple[str, bool, Optional[str]]:
|
|
167
|
-
"""
|
|
168
|
-
Translate text to target language with auto-detection of source language.
|
|
169
|
-
|
|
170
|
-
Args:
|
|
171
|
-
text: Text to translate
|
|
172
|
-
target_lang: Target language (name or code)
|
|
173
|
-
|
|
174
|
-
Returns:
|
|
175
|
-
Tuple of (translated_text, success, detected_source_lang)
|
|
176
|
-
"""
|
|
177
|
-
if not text or not text.strip():
|
|
178
|
-
return text, False, None
|
|
179
|
-
|
|
180
|
-
# Detect source language
|
|
181
|
-
detected = detect_language(text)
|
|
182
|
-
|
|
183
|
-
# Convert target to code
|
|
184
|
-
target_code = get_lang_code(target_lang)
|
|
185
|
-
|
|
186
|
-
# Skip if detected language matches target
|
|
187
|
-
if detected and detected == target_code:
|
|
188
|
-
return text, False, detected
|
|
189
|
-
|
|
190
|
-
# Translate
|
|
191
|
-
translated, success = translate(text, target_lang, source_lang=detected or 'en')
|
|
192
|
-
return translated, success, detected
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
def main():
|
|
196
|
-
"""CLI entry point for translator."""
|
|
197
|
-
if len(sys.argv) < 3:
|
|
198
|
-
print("Usage: translator.py <text> <target_language> [source_language]", file=sys.stderr)
|
|
199
|
-
print(" translator.py detect <text>", file=sys.stderr)
|
|
200
|
-
print("", file=sys.stderr)
|
|
201
|
-
print("Examples:", file=sys.stderr)
|
|
202
|
-
print(" translator.py 'Hello world' spanish", file=sys.stderr)
|
|
203
|
-
print(" translator.py 'Hello world' es en", file=sys.stderr)
|
|
204
|
-
print(" translator.py detect 'Hola mundo'", file=sys.stderr)
|
|
205
|
-
sys.exit(1)
|
|
206
|
-
|
|
207
|
-
command = sys.argv[1]
|
|
208
|
-
|
|
209
|
-
# Detection mode
|
|
210
|
-
if command == 'detect':
|
|
211
|
-
if len(sys.argv) < 3:
|
|
212
|
-
print("Usage: translator.py detect <text>", file=sys.stderr)
|
|
213
|
-
sys.exit(1)
|
|
214
|
-
text = sys.argv[2]
|
|
215
|
-
detected = detect_language(text)
|
|
216
|
-
if detected:
|
|
217
|
-
print(detected)
|
|
218
|
-
else:
|
|
219
|
-
print("unknown")
|
|
220
|
-
sys.exit(0)
|
|
221
|
-
|
|
222
|
-
# Translation mode
|
|
223
|
-
text = sys.argv[1]
|
|
224
|
-
target_lang = sys.argv[2]
|
|
225
|
-
source_lang = sys.argv[3] if len(sys.argv) > 3 else 'en'
|
|
226
|
-
|
|
227
|
-
translated, success = translate(text, target_lang, source_lang)
|
|
228
|
-
|
|
229
|
-
# Output the result (for shell script consumption)
|
|
230
|
-
print(translated)
|
|
231
|
-
|
|
232
|
-
# Exit with appropriate code
|
|
233
|
-
sys.exit(0 if success else 1)
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if __name__ == '__main__':
|
|
237
|
-
main()
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#
|
|
3
|
+
# File: .claude/hooks/translator.py
|
|
4
|
+
#
|
|
5
|
+
# AgentVibes - Finally, your AI Agents can Talk Back! Text-to-Speech WITH personality for AI Assistants!
|
|
6
|
+
# Website: https://agentvibes.org
|
|
7
|
+
# Repository: https://github.com/paulpreibisch/AgentVibes
|
|
8
|
+
#
|
|
9
|
+
# Co-created by Paul Preibisch with Claude AI
|
|
10
|
+
# Copyright (c) 2025 Paul Preibisch
|
|
11
|
+
#
|
|
12
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
13
|
+
# you may not use this file except in compliance with the License.
|
|
14
|
+
# You may obtain a copy of the License at
|
|
15
|
+
#
|
|
16
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
17
|
+
#
|
|
18
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
19
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
20
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21
|
+
# See the License for the specific language governing permissions and
|
|
22
|
+
# limitations under the License.
|
|
23
|
+
#
|
|
24
|
+
# DISCLAIMER: This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
25
|
+
# express or implied. Use at your own risk. See the Apache License for details.
|
|
26
|
+
#
|
|
27
|
+
# ---
|
|
28
|
+
#
|
|
29
|
+
# @fileoverview Text translator for multi-language TTS and learning mode
|
|
30
|
+
# @context Provides automatic translation using Google Translate via deep-translator library
|
|
31
|
+
# @architecture Standalone CLI module callable from bash scripts, with library mode for Python imports
|
|
32
|
+
# @dependencies deep-translator, langdetect (pip install deep-translator langdetect)
|
|
33
|
+
# @entrypoints CLI: python3 translator.py <text> <target_lang>, Library: from translator import translate
|
|
34
|
+
# @patterns Command pattern - supports translate, detect, and batch operations
|
|
35
|
+
# @related play-tts.sh, learn-manager.sh, language-manager.sh
|
|
36
|
+
#
|
|
37
|
+
|
|
38
|
+
"""
|
|
39
|
+
Text translation utilities for AgentVibes multi-language TTS.
|
|
40
|
+
|
|
41
|
+
Provides automatic translation of TTS text to the user's preferred language,
|
|
42
|
+
supporting both BMAD communication_language settings and learning mode.
|
|
43
|
+
|
|
44
|
+
Usage:
|
|
45
|
+
CLI: python3 translator.py <text> <target_language>
|
|
46
|
+
Library: from translator import translate, detect_language
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
import sys
|
|
50
|
+
import os
|
|
51
|
+
from typing import Optional, Tuple
|
|
52
|
+
|
|
53
|
+
# Language name to ISO code mapping
|
|
54
|
+
LANG_CODES = {
|
|
55
|
+
'spanish': 'es', 'español': 'es', 'es': 'es',
|
|
56
|
+
'french': 'fr', 'français': 'fr', 'fr': 'fr',
|
|
57
|
+
'german': 'de', 'deutsch': 'de', 'de': 'de',
|
|
58
|
+
'italian': 'it', 'italiano': 'it', 'it': 'it',
|
|
59
|
+
'portuguese': 'pt', 'português': 'pt', 'pt': 'pt',
|
|
60
|
+
'chinese': 'zh-CN', 'mandarin': 'zh-CN', 'zh': 'zh-CN', '中文': 'zh-CN',
|
|
61
|
+
'japanese': 'ja', '日本語': 'ja', 'ja': 'ja',
|
|
62
|
+
'korean': 'ko', '한국어': 'ko', 'ko': 'ko',
|
|
63
|
+
'russian': 'ru', 'русский': 'ru', 'ru': 'ru',
|
|
64
|
+
'polish': 'pl', 'polski': 'pl', 'pl': 'pl',
|
|
65
|
+
'dutch': 'nl', 'nederlands': 'nl', 'nl': 'nl',
|
|
66
|
+
'turkish': 'tr', 'türkçe': 'tr', 'tr': 'tr',
|
|
67
|
+
'arabic': 'ar', 'العربية': 'ar', 'ar': 'ar',
|
|
68
|
+
'hindi': 'hi', 'हिन्दी': 'hi', 'hi': 'hi',
|
|
69
|
+
'swedish': 'sv', 'svenska': 'sv', 'sv': 'sv',
|
|
70
|
+
'danish': 'da', 'dansk': 'da', 'da': 'da',
|
|
71
|
+
'norwegian': 'no', 'norsk': 'no', 'no': 'no',
|
|
72
|
+
'finnish': 'fi', 'suomi': 'fi', 'fi': 'fi',
|
|
73
|
+
'czech': 'cs', 'čeština': 'cs', 'cs': 'cs',
|
|
74
|
+
'romanian': 'ro', 'română': 'ro', 'ro': 'ro',
|
|
75
|
+
'ukrainian': 'uk', 'українська': 'uk', 'uk': 'uk',
|
|
76
|
+
'greek': 'el', 'ελληνικά': 'el', 'el': 'el',
|
|
77
|
+
'bulgarian': 'bg', 'български': 'bg', 'bg': 'bg',
|
|
78
|
+
'croatian': 'hr', 'hrvatski': 'hr', 'hr': 'hr',
|
|
79
|
+
'slovak': 'sk', 'slovenčina': 'sk', 'sk': 'sk',
|
|
80
|
+
'english': 'en', 'en': 'en',
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def get_lang_code(language: str) -> str:
|
|
85
|
+
"""
|
|
86
|
+
Convert language name to ISO code.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
language: Language name or code (e.g., 'spanish', 'es', 'español')
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
ISO language code (e.g., 'es')
|
|
93
|
+
"""
|
|
94
|
+
lang_lower = language.lower().strip()
|
|
95
|
+
return LANG_CODES.get(lang_lower, lang_lower)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def detect_language(text: str) -> Optional[str]:
|
|
99
|
+
"""
|
|
100
|
+
Detect the language of given text.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
text: Text to analyze
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Language code (e.g., 'es', 'fr', 'en') or None if detection fails
|
|
107
|
+
"""
|
|
108
|
+
if not text or len(text.strip()) < 3:
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
from langdetect import detect, LangDetectException
|
|
113
|
+
return detect(text)
|
|
114
|
+
except ImportError:
|
|
115
|
+
print("Warning: langdetect not installed. Run: pip install langdetect", file=sys.stderr)
|
|
116
|
+
return None
|
|
117
|
+
except Exception:
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def translate(text: str, target_lang: str, source_lang: str = 'en') -> Tuple[str, bool]:
|
|
122
|
+
"""
|
|
123
|
+
Translate text to target language.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
text: Text to translate
|
|
127
|
+
target_lang: Target language (name or code)
|
|
128
|
+
source_lang: Source language (default: 'en')
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Tuple of (translated_text, success)
|
|
132
|
+
"""
|
|
133
|
+
if not text or not text.strip():
|
|
134
|
+
return text, False
|
|
135
|
+
|
|
136
|
+
# Convert language names to codes
|
|
137
|
+
target_code = get_lang_code(target_lang)
|
|
138
|
+
source_code = get_lang_code(source_lang)
|
|
139
|
+
|
|
140
|
+
# Skip if source and target are the same
|
|
141
|
+
if target_code == source_code:
|
|
142
|
+
return text, False
|
|
143
|
+
|
|
144
|
+
# Skip if target is English and source is also English
|
|
145
|
+
if target_code == 'en' and source_code == 'en':
|
|
146
|
+
return text, False
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
from deep_translator import GoogleTranslator
|
|
150
|
+
|
|
151
|
+
translator = GoogleTranslator(source=source_code, target=target_code)
|
|
152
|
+
translated = translator.translate(text)
|
|
153
|
+
|
|
154
|
+
if translated:
|
|
155
|
+
return translated, True
|
|
156
|
+
return text, False
|
|
157
|
+
|
|
158
|
+
except ImportError:
|
|
159
|
+
print("Error: deep-translator not installed. Run: pip install deep-translator", file=sys.stderr)
|
|
160
|
+
return text, False
|
|
161
|
+
except Exception as e:
|
|
162
|
+
print(f"Translation error: {e}", file=sys.stderr)
|
|
163
|
+
return text, False
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def translate_auto(text: str, target_lang: str) -> Tuple[str, bool, Optional[str]]:
|
|
167
|
+
"""
|
|
168
|
+
Translate text to target language with auto-detection of source language.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
text: Text to translate
|
|
172
|
+
target_lang: Target language (name or code)
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
Tuple of (translated_text, success, detected_source_lang)
|
|
176
|
+
"""
|
|
177
|
+
if not text or not text.strip():
|
|
178
|
+
return text, False, None
|
|
179
|
+
|
|
180
|
+
# Detect source language
|
|
181
|
+
detected = detect_language(text)
|
|
182
|
+
|
|
183
|
+
# Convert target to code
|
|
184
|
+
target_code = get_lang_code(target_lang)
|
|
185
|
+
|
|
186
|
+
# Skip if detected language matches target
|
|
187
|
+
if detected and detected == target_code:
|
|
188
|
+
return text, False, detected
|
|
189
|
+
|
|
190
|
+
# Translate
|
|
191
|
+
translated, success = translate(text, target_lang, source_lang=detected or 'en')
|
|
192
|
+
return translated, success, detected
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def main():
|
|
196
|
+
"""CLI entry point for translator."""
|
|
197
|
+
if len(sys.argv) < 3:
|
|
198
|
+
print("Usage: translator.py <text> <target_language> [source_language]", file=sys.stderr)
|
|
199
|
+
print(" translator.py detect <text>", file=sys.stderr)
|
|
200
|
+
print("", file=sys.stderr)
|
|
201
|
+
print("Examples:", file=sys.stderr)
|
|
202
|
+
print(" translator.py 'Hello world' spanish", file=sys.stderr)
|
|
203
|
+
print(" translator.py 'Hello world' es en", file=sys.stderr)
|
|
204
|
+
print(" translator.py detect 'Hola mundo'", file=sys.stderr)
|
|
205
|
+
sys.exit(1)
|
|
206
|
+
|
|
207
|
+
command = sys.argv[1]
|
|
208
|
+
|
|
209
|
+
# Detection mode
|
|
210
|
+
if command == 'detect':
|
|
211
|
+
if len(sys.argv) < 3:
|
|
212
|
+
print("Usage: translator.py detect <text>", file=sys.stderr)
|
|
213
|
+
sys.exit(1)
|
|
214
|
+
text = sys.argv[2]
|
|
215
|
+
detected = detect_language(text)
|
|
216
|
+
if detected:
|
|
217
|
+
print(detected)
|
|
218
|
+
else:
|
|
219
|
+
print("unknown")
|
|
220
|
+
sys.exit(0)
|
|
221
|
+
|
|
222
|
+
# Translation mode
|
|
223
|
+
text = sys.argv[1]
|
|
224
|
+
target_lang = sys.argv[2]
|
|
225
|
+
source_lang = sys.argv[3] if len(sys.argv) > 3 else 'en'
|
|
226
|
+
|
|
227
|
+
translated, success = translate(text, target_lang, source_lang)
|
|
228
|
+
|
|
229
|
+
# Output the result (for shell script consumption)
|
|
230
|
+
print(translated)
|
|
231
|
+
|
|
232
|
+
# Exit with appropriate code
|
|
233
|
+
sys.exit(0 if success else 1)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
if __name__ == '__main__':
|
|
237
|
+
main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -513,6 +513,12 @@ case "$1" in
|
|
|
513
513
|
fi
|
|
514
514
|
fi
|
|
515
515
|
|
|
516
|
+
# Normalize to canonical path so ls output is consistent across platforms
|
|
517
|
+
# (Git Bash resolves /tmp→/c/Users/..., macOS has /var→/private/var)
|
|
518
|
+
if [[ -d "$AUDIO_DIR" ]]; then
|
|
519
|
+
AUDIO_DIR=$(cd "$AUDIO_DIR" && pwd -P)
|
|
520
|
+
fi
|
|
521
|
+
|
|
516
522
|
# Default to replay last audio (N=1)
|
|
517
523
|
N="${2:-1}"
|
|
518
524
|
|
|
@@ -62,15 +62,20 @@ if (-not $VoiceName) {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
# Security: Validate voice name to prevent path traversal
|
|
65
|
-
#
|
|
66
|
-
|
|
65
|
+
# Format: <model> or <model>::<speaker> for multi-speaker Piper models
|
|
66
|
+
# Only allow alphanumeric, underscore, hyphen, period, and the :: separator
|
|
67
|
+
if ($VoiceName -notmatch '^[a-zA-Z0-9_\-\.]+(::[a-zA-Z0-9_\-\.]+)?$') {
|
|
67
68
|
Write-Host "[ERROR] Invalid voice name: $VoiceName" -ForegroundColor Red
|
|
68
69
|
exit 1
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
# Extract model name and optional speaker (multi-speaker format: model::Speaker)
|
|
73
|
+
$VoiceModel = ($VoiceName -split '::')[0]
|
|
74
|
+
$SpeakerName = if ($VoiceName -match '::(.+)$') { $Matches[1] } else { "" }
|
|
75
|
+
|
|
71
76
|
# Resolve voice model path and validate it stays within VoicesDir
|
|
72
|
-
$VoiceModelFile = [System.IO.Path]::GetFullPath("$VoicesDir\$
|
|
73
|
-
$VoiceJsonFile
|
|
77
|
+
$VoiceModelFile = [System.IO.Path]::GetFullPath("$VoicesDir\$VoiceModel.onnx")
|
|
78
|
+
$VoiceJsonFile = [System.IO.Path]::GetFullPath("$VoicesDir\$VoiceModel.onnx.json")
|
|
74
79
|
$ResolvedVoicesDir = [System.IO.Path]::GetFullPath($VoicesDir)
|
|
75
80
|
if (-not $VoiceModelFile.StartsWith($ResolvedVoicesDir)) {
|
|
76
81
|
Write-Host "[ERROR] Voice path outside voices directory" -ForegroundColor Red
|
|
@@ -79,15 +84,15 @@ if (-not $VoiceModelFile.StartsWith($ResolvedVoicesDir)) {
|
|
|
79
84
|
|
|
80
85
|
# Check if voice model exists, download if missing
|
|
81
86
|
if (-not (Test-Path $VoiceModelFile)) {
|
|
82
|
-
Write-Host "[DOWNLOAD] Voice model: $
|
|
87
|
+
Write-Host "[DOWNLOAD] Voice model: $VoiceModel" -ForegroundColor Yellow
|
|
83
88
|
|
|
84
89
|
# Try to download from Hugging Face
|
|
85
90
|
# Voice name format: {lang}_{region}-{speaker}-{quality}
|
|
86
91
|
# HF path format: {lang}/{lang}_{region}/{speaker}/{quality}/{voicename}.onnx
|
|
87
92
|
try {
|
|
88
|
-
# Parse
|
|
93
|
+
# Parse model name to build correct HF path (strip ::Speaker suffix first)
|
|
89
94
|
# e.g. en_US-ryan-high -> en/en_US/ryan/high/en_US-ryan-high.onnx
|
|
90
|
-
if ($
|
|
95
|
+
if ($VoiceModel -match '^([a-z]{2})_([A-Z]{2})-([a-zA-Z0-9_]+)-([a-z]+)$') {
|
|
91
96
|
$Lang = $Matches[1]
|
|
92
97
|
$LangRegion = "$($Matches[1])_$($Matches[2])"
|
|
93
98
|
$Speaker = $Matches[3]
|
|
@@ -97,8 +102,8 @@ if (-not (Test-Path $VoiceModelFile)) {
|
|
|
97
102
|
# Fallback for non-standard voice names
|
|
98
103
|
$HFBase = "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/ryan/high"
|
|
99
104
|
}
|
|
100
|
-
$ModelUrl = "$HFBase/$
|
|
101
|
-
$JsonUrl = "$HFBase/$
|
|
105
|
+
$ModelUrl = "$HFBase/$VoiceModel.onnx"
|
|
106
|
+
$JsonUrl = "$HFBase/$VoiceModel.onnx.json"
|
|
102
107
|
|
|
103
108
|
Write-Host " Downloading model..." -ForegroundColor Cyan
|
|
104
109
|
Invoke-WebRequest -Uri $ModelUrl -OutFile $VoiceModelFile -ErrorAction Stop
|
|
@@ -127,11 +132,10 @@ $AudioFile = "$AudioDir\tts-$Timestamp.wav"
|
|
|
127
132
|
try {
|
|
128
133
|
Write-Host "[SYNTH] Synthesizing with Piper..." -ForegroundColor Cyan
|
|
129
134
|
|
|
130
|
-
# Run Piper with text input
|
|
131
|
-
$
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
2>$null
|
|
135
|
+
# Run Piper with text input; pass --speaker for multi-speaker models (model::Speaker format)
|
|
136
|
+
$piperArgs = @('--model', $VoiceModelFile, '--output-file', $AudioFile)
|
|
137
|
+
if ($SpeakerName) { $piperArgs += @('--speaker', $SpeakerName) }
|
|
138
|
+
$Text | & $PiperExe @piperArgs 2>$null
|
|
135
139
|
|
|
136
140
|
if (-not (Test-Path $AudioFile)) {
|
|
137
141
|
Write-Host "[ERROR] Piper synthesis failed" -ForegroundColor Red
|
|
@@ -151,12 +155,14 @@ try {
|
|
|
151
155
|
if (-not $env:AGENTVIBES_NO_PLAY) {
|
|
152
156
|
# Prefer ffplay: handles 22050 Hz → 48000 Hz resampling cleanly (SoundPlayer uses
|
|
153
157
|
# WinMM's low-quality resampler which produces choppy audio at non-native rates).
|
|
154
|
-
$
|
|
158
|
+
$ffplayCmd = Get-Command ffplay -ErrorAction SilentlyContinue
|
|
159
|
+
$ffplayPath = if ($ffplayCmd) { $ffplayCmd.Source }
|
|
155
160
|
if (-not $ffplayPath) {
|
|
156
161
|
# SSH/watcher sessions may have a minimal PATH — refresh from registry
|
|
157
162
|
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" +
|
|
158
163
|
[System.Environment]::GetEnvironmentVariable("Path","User")
|
|
159
|
-
$
|
|
164
|
+
$ffplayCmd = Get-Command ffplay -ErrorAction SilentlyContinue
|
|
165
|
+
$ffplayPath = if ($ffplayCmd) { $ffplayCmd.Source }
|
|
160
166
|
}
|
|
161
167
|
if ($ffplayPath) {
|
|
162
168
|
& $ffplayPath -autoexit -nodisp -loglevel quiet $AudioFile 2>$null
|