termux-tts 1.4.3 → 1.4.4
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/.github/workflows/release.yml +98 -98
- package/CHANGELOG.md +69 -57
- package/LICENSE +17 -17
- package/README.md +138 -441
- package/README.pypi.md +54 -441
- package/bin/cli.js +33 -33
- package/doc.config.yaml +596 -532
- package/docs/benchmarks.md +11 -11
- package/index.js +5 -5
- package/install.sh +53 -53
- package/package.json +57 -57
- package/pyproject.toml +81 -81
- package/setup.py +38 -38
- package/termux_tts/__init__.py +74 -64
- package/termux_tts/audio.py +6 -3
- package/termux_tts/cli.py +27 -10
- package/termux_tts/control/component.py +398 -398
- package/termux_tts/engine.py +73 -7
- package/termux_tts/engine_multilingual.py +374 -0
- package/termux_tts/engine_native.py +104 -104
- package/termux_tts/engine_sherpa.py +2 -1
- package/termux_tts/engine_sherpa_capi.py +491 -0
- package/termux_tts/exceptions.py +28 -28
- package/termux_tts/hardware.py +34 -1
- package/termux_tts/installer.py +154 -12
- package/termux_tts/script_classifier.py +253 -0
- package/termux_tts/tokenizer.py +186 -184
|
@@ -1,104 +1,104 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Android OS System Native TTS Engine Bridge (Option B).
|
|
3
|
-
Directly communicates with Samsung / Google Voice Engine via Termux IPC.
|
|
4
|
-
Provides authentic human speech output with zero download overhead.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import os
|
|
8
|
-
import time
|
|
9
|
-
import subprocess
|
|
10
|
-
import shutil
|
|
11
|
-
from dataclasses import dataclass
|
|
12
|
-
from typing import Optional
|
|
13
|
-
|
|
14
|
-
from .exceptions import TTSInferenceError
|
|
15
|
-
|
|
16
|
-
@dataclass
|
|
17
|
-
class NativeResult:
|
|
18
|
-
text: str
|
|
19
|
-
output_path: Optional[str]
|
|
20
|
-
language: str
|
|
21
|
-
pitch: float
|
|
22
|
-
rate: float
|
|
23
|
-
elapsed_ms: float
|
|
24
|
-
engine_name: str
|
|
25
|
-
|
|
26
|
-
class NativeAndroidEngine:
|
|
27
|
-
"""Android System Native TTS Engine Bridge (Samsung / Google Voice)."""
|
|
28
|
-
|
|
29
|
-
def __init__(
|
|
30
|
-
self,
|
|
31
|
-
language: str = "ko",
|
|
32
|
-
pitch: float = 1.0,
|
|
33
|
-
rate: float = 1.0,
|
|
34
|
-
stream: str = "MUSIC"
|
|
35
|
-
):
|
|
36
|
-
self.language = language
|
|
37
|
-
self.pitch = pitch
|
|
38
|
-
self.rate = rate
|
|
39
|
-
self.stream = stream
|
|
40
|
-
self.binary = self._find_binary()
|
|
41
|
-
|
|
42
|
-
def _find_binary(self) -> Optional[str]:
|
|
43
|
-
bin_path = shutil.which("termux-tts-speak")
|
|
44
|
-
if bin_path and os.access(bin_path, os.X_OK):
|
|
45
|
-
return bin_path
|
|
46
|
-
default_p = "/data/data/com.termux/files/usr/bin/termux-tts-speak"
|
|
47
|
-
if os.path.exists(default_p) and os.access(default_p, os.X_OK):
|
|
48
|
-
return default_p
|
|
49
|
-
return None
|
|
50
|
-
|
|
51
|
-
def speak(self, text: str, stream: Optional[str] = None) -> NativeResult:
|
|
52
|
-
"""Speak text directly through physical Android device speakers via termux-tts-speak IPC."""
|
|
53
|
-
if not text or not text.strip():
|
|
54
|
-
raise TTSInferenceError("Input text cannot be empty or whitespace only.")
|
|
55
|
-
|
|
56
|
-
if not self.binary:
|
|
57
|
-
raise TTSInferenceError(
|
|
58
|
-
"[FAIL-FAST] 'termux-tts-speak' binary not found on this system. "
|
|
59
|
-
"NativeAndroidEngine requires an Android Termux environment with 'termux-api' installed (run 'pkg install termux-api'). "
|
|
60
|
-
"If running on non-Android Linux/macOS/Windows, please use '--engine dsp' or '--engine onnx'."
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
t0 = time.perf_counter()
|
|
64
|
-
target_stream = stream or self.stream
|
|
65
|
-
cmd = [
|
|
66
|
-
self.binary,
|
|
67
|
-
"-l", self.language,
|
|
68
|
-
"-p", str(self.pitch),
|
|
69
|
-
"-r", str(self.rate),
|
|
70
|
-
"-s", target_stream,
|
|
71
|
-
text
|
|
72
|
-
]
|
|
73
|
-
|
|
74
|
-
try:
|
|
75
|
-
res = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
76
|
-
if res.returncode != 0:
|
|
77
|
-
err_msg = res.stderr.strip() or f"Process exited with code {res.returncode}"
|
|
78
|
-
raise TTSInferenceError(f"Native Android TTS engine execution failed: {err_msg}")
|
|
79
|
-
except subprocess.TimeoutExpired as e:
|
|
80
|
-
raise TTSInferenceError(f"Native Android TTS execution timed out (30s): {e}") from e
|
|
81
|
-
except Exception as e:
|
|
82
|
-
raise TTSInferenceError(f"Failed to execute native Android TTS: {e}") from e
|
|
83
|
-
|
|
84
|
-
elapsed_ms = (time.perf_counter() - t0) * 1000.0
|
|
85
|
-
return NativeResult(
|
|
86
|
-
text=text,
|
|
87
|
-
output_path=None,
|
|
88
|
-
language=self.language,
|
|
89
|
-
pitch=self.pitch,
|
|
90
|
-
rate=self.rate,
|
|
91
|
-
elapsed_ms=elapsed_ms,
|
|
92
|
-
engine_name="Android_Native_Voice_Engine"
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
def close(self) -> None:
|
|
96
|
-
pass
|
|
97
|
-
|
|
98
|
-
def __enter__(self):
|
|
99
|
-
return self
|
|
100
|
-
|
|
101
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
102
|
-
self.close()
|
|
103
|
-
|
|
104
|
-
|
|
1
|
+
"""
|
|
2
|
+
Android OS System Native TTS Engine Bridge (Option B).
|
|
3
|
+
Directly communicates with Samsung / Google Voice Engine via Termux IPC.
|
|
4
|
+
Provides authentic human speech output with zero download overhead.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import time
|
|
9
|
+
import subprocess
|
|
10
|
+
import shutil
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
14
|
+
from .exceptions import TTSInferenceError
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class NativeResult:
|
|
18
|
+
text: str
|
|
19
|
+
output_path: Optional[str]
|
|
20
|
+
language: str
|
|
21
|
+
pitch: float
|
|
22
|
+
rate: float
|
|
23
|
+
elapsed_ms: float
|
|
24
|
+
engine_name: str
|
|
25
|
+
|
|
26
|
+
class NativeAndroidEngine:
|
|
27
|
+
"""Android System Native TTS Engine Bridge (Samsung / Google Voice)."""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
language: str = "ko",
|
|
32
|
+
pitch: float = 1.0,
|
|
33
|
+
rate: float = 1.0,
|
|
34
|
+
stream: str = "MUSIC"
|
|
35
|
+
):
|
|
36
|
+
self.language = language
|
|
37
|
+
self.pitch = pitch
|
|
38
|
+
self.rate = rate
|
|
39
|
+
self.stream = stream
|
|
40
|
+
self.binary = self._find_binary()
|
|
41
|
+
|
|
42
|
+
def _find_binary(self) -> Optional[str]:
|
|
43
|
+
bin_path = shutil.which("termux-tts-speak")
|
|
44
|
+
if bin_path and os.access(bin_path, os.X_OK):
|
|
45
|
+
return bin_path
|
|
46
|
+
default_p = "/data/data/com.termux/files/usr/bin/termux-tts-speak"
|
|
47
|
+
if os.path.exists(default_p) and os.access(default_p, os.X_OK):
|
|
48
|
+
return default_p
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
def speak(self, text: str, stream: Optional[str] = None) -> NativeResult:
|
|
52
|
+
"""Speak text directly through physical Android device speakers via termux-tts-speak IPC."""
|
|
53
|
+
if not text or not text.strip():
|
|
54
|
+
raise TTSInferenceError("Input text cannot be empty or whitespace only.")
|
|
55
|
+
|
|
56
|
+
if not self.binary:
|
|
57
|
+
raise TTSInferenceError(
|
|
58
|
+
"[FAIL-FAST] 'termux-tts-speak' binary not found on this system. "
|
|
59
|
+
"NativeAndroidEngine requires an Android Termux environment with 'termux-api' installed (run 'pkg install termux-api'). "
|
|
60
|
+
"If running on non-Android Linux/macOS/Windows, please use '--engine dsp' or '--engine onnx'."
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
t0 = time.perf_counter()
|
|
64
|
+
target_stream = stream or self.stream
|
|
65
|
+
cmd = [
|
|
66
|
+
self.binary,
|
|
67
|
+
"-l", self.language,
|
|
68
|
+
"-p", str(self.pitch),
|
|
69
|
+
"-r", str(self.rate),
|
|
70
|
+
"-s", target_stream,
|
|
71
|
+
text
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
res = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
76
|
+
if res.returncode != 0:
|
|
77
|
+
err_msg = res.stderr.strip() or f"Process exited with code {res.returncode}"
|
|
78
|
+
raise TTSInferenceError(f"Native Android TTS engine execution failed: {err_msg}")
|
|
79
|
+
except subprocess.TimeoutExpired as e:
|
|
80
|
+
raise TTSInferenceError(f"Native Android TTS execution timed out (30s): {e}") from e
|
|
81
|
+
except Exception as e:
|
|
82
|
+
raise TTSInferenceError(f"Failed to execute native Android TTS: {e}") from e
|
|
83
|
+
|
|
84
|
+
elapsed_ms = (time.perf_counter() - t0) * 1000.0
|
|
85
|
+
return NativeResult(
|
|
86
|
+
text=text,
|
|
87
|
+
output_path=None,
|
|
88
|
+
language=self.language,
|
|
89
|
+
pitch=self.pitch,
|
|
90
|
+
rate=self.rate,
|
|
91
|
+
elapsed_ms=elapsed_ms,
|
|
92
|
+
engine_name="Android_Native_Voice_Engine"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def close(self) -> None:
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
def __enter__(self):
|
|
99
|
+
return self
|
|
100
|
+
|
|
101
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
102
|
+
self.close()
|
|
103
|
+
|
|
104
|
+
|
|
@@ -109,7 +109,8 @@ class SherpaNeuralEngine:
|
|
|
109
109
|
elif p.is_file():
|
|
110
110
|
search_dirs = [p.parent]
|
|
111
111
|
else:
|
|
112
|
-
|
|
112
|
+
from .hardware import get_unified_model_search_dirs
|
|
113
|
+
search_dirs = list(get_unified_model_search_dirs("tts"))
|
|
113
114
|
|
|
114
115
|
# 1. Find directory containing .onnx model, tokens.txt, and espeak-ng-data
|
|
115
116
|
for sdir in search_dirs:
|