termux-stt 1.4.0 → 2.0.0
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/README.md +84 -117
- package/README.pypi.md +44 -44
- package/bin/cli.js +51 -51
- package/bin/termux-stt.js +51 -51
- package/index.d.ts +8 -3
- package/index.js +8 -6
- package/lib/hybrid.js +1 -1
- package/lib/sherpa.js +97 -0
- package/package.json +87 -87
- package/lib/vosk.js +0 -60
package/README.md
CHANGED
|
@@ -1,117 +1,84 @@
|
|
|
1
|
-
# Termux-STT
|
|
2
|
-
|
|
3
|
-
[](https://pypi.org/project/termux-stt/)
|
|
4
|
-
[](https://pypi.org/project/termux-stt/)
|
|
5
|
-
[](https://www.npmjs.com/package/termux-stt)
|
|
6
|
-
[](https://www.npmjs.com/package/termux-stt)
|
|
7
|
-
[](https://github.com/uno-km/termux-stt)
|
|
8
|
-
|
|
9
|
-
> **디바이스 리소스를 활용한 통합 온디바이스 음성인식(STT) 및
|
|
10
|
-
> *Unified On-Device Speech-to-Text
|
|
11
|
-
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
## Architecture & Overview
|
|
15
|
-
|
|
16
|
-
Whisper.cpp
|
|
17
|
-
|
|
18
|
-
Integrates Whisper.cpp
|
|
19
|
-
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
## Installation & Quickstart
|
|
23
|
-
|
|
24
|
-
### Python (PyPI)
|
|
25
|
-
```bash
|
|
26
|
-
pip install termux-stt
|
|
27
|
-
```
|
|
28
|
-
```python
|
|
29
|
-
from termux_stt import create_engine
|
|
30
|
-
|
|
31
|
-
# 1. Initialize Engine with
|
|
32
|
-
engine = create_engine("whisper", model="small", lang="
|
|
33
|
-
|
|
34
|
-
# 2. Transcribe Audio directly into Subtitles
|
|
35
|
-
result = engine.transcribe("samples/jfk_1min.wav")
|
|
36
|
-
print("Transcript:\n", result.text)
|
|
37
|
-
print("SRT Subtitles:\n", result.to_srt())
|
|
38
|
-
|
|
39
|
-
# 3.
|
|
40
|
-
hybrid = create_engine("hybrid", lang="
|
|
41
|
-
diar_result = hybrid.diarize("samples/
|
|
42
|
-
for seg in diar_result.segments:
|
|
43
|
-
print(f"[{seg.speaker}] ({seg.start:.1f}s -> {seg.end:.1f}s): {seg.text}")
|
|
44
|
-
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
### Node.js / TypeScript (npm)
|
|
48
|
-
```bash
|
|
49
|
-
npm install termux-stt
|
|
50
|
-
```
|
|
51
|
-
```typescript
|
|
52
|
-
const { createEngine } = require("termux-stt");
|
|
53
|
-
|
|
54
|
-
async function main() {
|
|
55
|
-
// 1. Initialize Whisper Engine
|
|
56
|
-
const engine = createEngine("whisper", { model: "tiny", lang: "
|
|
57
|
-
|
|
58
|
-
// 2. Transcribe Audio
|
|
59
|
-
const result = await engine.transcribe("samples/jfk_1min.wav");
|
|
60
|
-
console.log("Transcript:", result.text);
|
|
61
|
-
console.log("SRT Subtitles:\n", result.toSrt());
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
### 3. Distributed Transcription via Master Node
|
|
87
|
-
```bash
|
|
88
|
-
# Master node sharding Whisper-Large layers across remote phone RAM:
|
|
89
|
-
termux-stt transcribe meeting.wav \
|
|
90
|
-
--model whisper-large-v3-turbo.bin \
|
|
91
|
-
--rpc 192.0.2.10:50052,192.0.2.11:50052
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
```python
|
|
95
|
-
from termux_stt import create_engine
|
|
96
|
-
|
|
97
|
-
# Python SDK Distributed STT
|
|
98
|
-
engine = create_engine(
|
|
99
|
-
"whisper",
|
|
100
|
-
model="large-v3-turbo",
|
|
101
|
-
cluster_rpc_servers="192.0.2.10:50052,192.0.2.11:50052"
|
|
102
|
-
)
|
|
103
|
-
result = engine.transcribe("meeting.wav")
|
|
104
|
-
print(result.text)
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
---
|
|
108
|
-
|
|
109
|
-
## Official Documentation & Benchmarks
|
|
110
|
-
- [Official Architecture & API Reference](https://uno-km.vercel.app/lib/stt/)
|
|
111
|
-
- [Ecosystem Metrics & Registry Stats](https://uno-km.vercel.app/foundation/metrics)
|
|
112
|
-
- [AMEVA Open-Source Foundation Portal](https://uno-km.vercel.app/foundation/index.html)
|
|
113
|
-
|
|
114
|
-
---
|
|
115
|
-
|
|
116
|
-
## License
|
|
117
|
-
Licensed under the Apache-2.0 License. Copyright (c) 2026 Eunho Kim ([@uno-km](https://github.com/uno-km)).
|
|
1
|
+
# Termux-STT
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/termux-stt/)
|
|
4
|
+
[](https://pypi.org/project/termux-stt/)
|
|
5
|
+
[](https://www.npmjs.com/package/termux-stt)
|
|
6
|
+
[](https://www.npmjs.com/package/termux-stt)
|
|
7
|
+
[](https://github.com/uno-km/termux-stt)
|
|
8
|
+
|
|
9
|
+
> **디바이스 리소스를 활용한 통합 온디바이스 음성인식(STT) 및 차세대 신경망 화자 분리(PyAnnote 3.0 + CAM++ 192d + TS-VAD) 프레임워크**
|
|
10
|
+
> *Unified On-Device Speech-to-Text & Neural Multi-Speaker Diarization (PyAnnote 3.0 + CAM++ 192d + TS-VAD Overlap Resolver)*
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Architecture & Overview
|
|
15
|
+
|
|
16
|
+
Whisper.cpp(네이티브 Vulkan GPU / NEON CPU) 및 Sherpa-ONNX 듀얼 모던 엔진을 통합하고, PyAnnote 3.0 신경망 세그멘테이션, 3D-Speaker CAM++ 192차원 성문 임베딩, TS-VAD 동시 발화(마이크 물림) 분리기를 결합하여 완전 온디바이스 실시간 다중 화자 음성인식을 구현합니다.
|
|
17
|
+
|
|
18
|
+
Integrates Whisper.cpp (Native Vulkan GPU / NEON CPU) and Sherpa-ONNX with PyAnnote 3.0 neural segmentation, 3D-Speaker CAM++ 192-dim embeddings, and TS-VAD overlapped speech resolution with zero cloud egress.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Installation & Quickstart
|
|
23
|
+
|
|
24
|
+
### Python (PyPI)
|
|
25
|
+
```bash
|
|
26
|
+
pip install termux-stt
|
|
27
|
+
```
|
|
28
|
+
```python
|
|
29
|
+
from termux_stt import create_engine
|
|
30
|
+
|
|
31
|
+
# 1. Initialize Whisper Engine with Vulkan GPU / CPU Hybrid Acceleration
|
|
32
|
+
engine = create_engine("whisper", model="small", lang="ko", threads=4, split_mode=True)
|
|
33
|
+
|
|
34
|
+
# 2. Transcribe Audio directly into Subtitles
|
|
35
|
+
result = engine.transcribe("samples/jfk_1min.wav")
|
|
36
|
+
print("Transcript:\n", result.text)
|
|
37
|
+
print("SRT Subtitles:\n", result.to_srt())
|
|
38
|
+
|
|
39
|
+
# 3. Multi-Speaker Neural Diarization (PyAnnote 3.0 + CAM++ 192d + TS-VAD)
|
|
40
|
+
hybrid = create_engine("hybrid", lang="ko", num_speakers=2)
|
|
41
|
+
diar_result = hybrid.diarize("samples/kor_영어로화자분리.wav")
|
|
42
|
+
for seg in diar_result.segments:
|
|
43
|
+
print(f"[{seg.speaker}] ({seg.start:.1f}s -> {seg.end:.1f}s): {seg.text}")
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Node.js / TypeScript (npm)
|
|
48
|
+
```bash
|
|
49
|
+
npm install termux-stt
|
|
50
|
+
```
|
|
51
|
+
```typescript
|
|
52
|
+
const { createEngine } = require("termux-stt");
|
|
53
|
+
|
|
54
|
+
async function main() {
|
|
55
|
+
// 1. Initialize Whisper Engine
|
|
56
|
+
const engine = createEngine("whisper", { model: "tiny", lang: "ko", threads: 4 });
|
|
57
|
+
|
|
58
|
+
// 2. Transcribe Audio
|
|
59
|
+
const result = await engine.transcribe("samples/jfk_1min.wav");
|
|
60
|
+
console.log("Transcript:", result.text);
|
|
61
|
+
console.log("SRT Subtitles:\n", result.toSrt());
|
|
62
|
+
|
|
63
|
+
// 3. Neural Diarization via Hybrid Engine
|
|
64
|
+
const hybrid = createEngine("hybrid", { lang: "ko", numSpeakers: 2 });
|
|
65
|
+
const diarResult = await hybrid.diarize("samples/kor_영어로화자분리.wav");
|
|
66
|
+
for (const seg of diarResult.segments) {
|
|
67
|
+
console.log(`[${seg.speaker}] (${seg.start.toFixed(1)}s -> ${seg.end.toFixed(1)}s): ${seg.text}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
main();
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Official Documentation & Benchmarks
|
|
77
|
+
- [Official Architecture & API Reference](https://uno-km.vercel.app/lib/stt/)
|
|
78
|
+
- [Ecosystem Metrics & Registry Stats](https://uno-km.vercel.app/foundation/metrics)
|
|
79
|
+
- [AMEVA Open-Source Foundation Portal](https://uno-km.vercel.app/foundation/index.html)
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## License
|
|
84
|
+
Licensed under the Apache-2.0 License. Copyright (c) 2026 Eunho Kim ([@uno-km](https://github.com/uno-km)).
|
package/README.pypi.md
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
# Termux-STT (Python)
|
|
2
|
-
|
|
3
|
-
[](https://pypi.org/project/termux-stt/)
|
|
4
|
-
[](https://pypi.org/project/termux-stt/)
|
|
5
|
-
[](https://github.com/uno-km/termux-stt)
|
|
6
|
-
|
|
7
|
-
> Unified On-Device Speech-to-Text
|
|
8
|
-
|
|
9
|
-
## Installation
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
pip install termux-stt
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## Quickstart
|
|
16
|
-
|
|
17
|
-
```python
|
|
18
|
-
from termux_stt import create_engine
|
|
19
|
-
|
|
20
|
-
# 1. Initialize Engine with
|
|
21
|
-
engine = create_engine("whisper", model="small", lang="
|
|
22
|
-
|
|
23
|
-
# 2. Transcribe Audio directly into Subtitles
|
|
24
|
-
result = engine.transcribe("samples/jfk_1min.wav")
|
|
25
|
-
print("Transcript:\n", result.text)
|
|
26
|
-
print("SRT Subtitles:\n", result.to_srt())
|
|
27
|
-
|
|
28
|
-
# 3.
|
|
29
|
-
hybrid = create_engine("hybrid", lang="
|
|
30
|
-
diar_result = hybrid.diarize("samples/
|
|
31
|
-
for seg in diar_result.segments:
|
|
32
|
-
print(f"[{seg.speaker}] ({seg.start:.1f}s -> {seg.end:.1f}s): {seg.text}")
|
|
33
|
-
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## Description
|
|
37
|
-
Integrates Whisper.cpp
|
|
38
|
-
|
|
39
|
-
## Documentation
|
|
40
|
-
- [Official Documentation & API Reference](https://uno-km.vercel.app/lib/stt/)
|
|
41
|
-
- [GitHub Repository](https://github.com/uno-km/termux-stt)
|
|
42
|
-
|
|
43
|
-
## License
|
|
44
|
-
Apache-2.0 License. Copyright (c) 2026 Eunho Kim (@uno-km).
|
|
1
|
+
# Termux-STT (Python)
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/termux-stt/)
|
|
4
|
+
[](https://pypi.org/project/termux-stt/)
|
|
5
|
+
[](https://github.com/uno-km/termux-stt)
|
|
6
|
+
|
|
7
|
+
> Unified On-Device Speech-to-Text & Neural Multi-Speaker Diarization (PyAnnote 3.0 + CAM++ 192d + TS-VAD Overlap Resolver)
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install termux-stt
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quickstart
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from termux_stt import create_engine
|
|
19
|
+
|
|
20
|
+
# 1. Initialize Whisper Engine with Vulkan GPU / CPU Hybrid Acceleration
|
|
21
|
+
engine = create_engine("whisper", model="small", lang="ko", threads=4, split_mode=True)
|
|
22
|
+
|
|
23
|
+
# 2. Transcribe Audio directly into Subtitles
|
|
24
|
+
result = engine.transcribe("samples/jfk_1min.wav")
|
|
25
|
+
print("Transcript:\n", result.text)
|
|
26
|
+
print("SRT Subtitles:\n", result.to_srt())
|
|
27
|
+
|
|
28
|
+
# 3. Multi-Speaker Neural Diarization (PyAnnote 3.0 + CAM++ 192d + TS-VAD)
|
|
29
|
+
hybrid = create_engine("hybrid", lang="ko", num_speakers=2)
|
|
30
|
+
diar_result = hybrid.diarize("samples/kor_영어로화자분리.wav")
|
|
31
|
+
for seg in diar_result.segments:
|
|
32
|
+
print(f"[{seg.speaker}] ({seg.start:.1f}s -> {seg.end:.1f}s): {seg.text}")
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Description
|
|
37
|
+
Integrates Whisper.cpp (Native Vulkan GPU / NEON CPU) and Sherpa-ONNX with PyAnnote 3.0 neural segmentation, 3D-Speaker CAM++ 192-dim embeddings, and TS-VAD overlapped speech resolution with zero cloud egress.
|
|
38
|
+
|
|
39
|
+
## Documentation
|
|
40
|
+
- [Official Documentation & API Reference](https://uno-km.vercel.app/lib/stt/)
|
|
41
|
+
- [GitHub Repository](https://github.com/uno-km/termux-stt)
|
|
42
|
+
|
|
43
|
+
## License
|
|
44
|
+
Apache-2.0 License. Copyright (c) 2026 Eunho Kim (@uno-km).
|
package/bin/cli.js
CHANGED
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* AMEVA Standard Node.js CLI Runner for termux_stt.
|
|
4
|
-
* Automatically resolves Python 3 environment and dispatches to python -m termux_stt.
|
|
5
|
-
*/
|
|
6
|
-
const fs = require('fs');
|
|
7
|
-
const { spawn, execSync } = require('child_process');
|
|
8
|
-
|
|
9
|
-
function findPython() {
|
|
10
|
-
if (process.env.PYTHON && fs.existsSync(process.env.PYTHON)) {
|
|
11
|
-
return process.env.PYTHON;
|
|
12
|
-
}
|
|
13
|
-
const termuxBin = '/data/data/com.termux/files/usr/bin/python3';
|
|
14
|
-
if (fs.existsSync(termuxBin)) {
|
|
15
|
-
return termuxBin;
|
|
16
|
-
}
|
|
17
|
-
const termuxBinAlt = '/data/data/com.termux/files/usr/bin/python';
|
|
18
|
-
if (fs.existsSync(termuxBinAlt)) {
|
|
19
|
-
return termuxBinAlt;
|
|
20
|
-
}
|
|
21
|
-
const candidates = ['python3', 'python'];
|
|
22
|
-
for (const cmd of candidates) {
|
|
23
|
-
try {
|
|
24
|
-
const checkCmd = process.platform === 'win32' ? `where ${cmd}` : `command -v ${cmd}`;
|
|
25
|
-
const res = execSync(checkCmd, { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
|
|
26
|
-
if (res) return cmd;
|
|
27
|
-
} catch (_) {}
|
|
28
|
-
}
|
|
29
|
-
return 'python3';
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const pythonBin = findPython();
|
|
33
|
-
const args = ['-m', 'termux_stt', ...process.argv.slice(2)];
|
|
34
|
-
|
|
35
|
-
const child = spawn(pythonBin, args, {
|
|
36
|
-
stdio: 'inherit',
|
|
37
|
-
env: process.env
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
child.on('error', (err) => {
|
|
41
|
-
console.error(`[${'termux_stt'}] Failed to spawn python process (${pythonBin}):`, err.message);
|
|
42
|
-
process.exit(1);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
child.on('exit', (code, signal) => {
|
|
46
|
-
if (signal) {
|
|
47
|
-
process.kill(process.pid, signal);
|
|
48
|
-
} else {
|
|
49
|
-
process.exit(code || 0);
|
|
50
|
-
}
|
|
51
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* AMEVA Standard Node.js CLI Runner for termux_stt.
|
|
4
|
+
* Automatically resolves Python 3 environment and dispatches to python -m termux_stt.
|
|
5
|
+
*/
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const { spawn, execSync } = require('child_process');
|
|
8
|
+
|
|
9
|
+
function findPython() {
|
|
10
|
+
if (process.env.PYTHON && fs.existsSync(process.env.PYTHON)) {
|
|
11
|
+
return process.env.PYTHON;
|
|
12
|
+
}
|
|
13
|
+
const termuxBin = '/data/data/com.termux/files/usr/bin/python3';
|
|
14
|
+
if (fs.existsSync(termuxBin)) {
|
|
15
|
+
return termuxBin;
|
|
16
|
+
}
|
|
17
|
+
const termuxBinAlt = '/data/data/com.termux/files/usr/bin/python';
|
|
18
|
+
if (fs.existsSync(termuxBinAlt)) {
|
|
19
|
+
return termuxBinAlt;
|
|
20
|
+
}
|
|
21
|
+
const candidates = ['python3', 'python'];
|
|
22
|
+
for (const cmd of candidates) {
|
|
23
|
+
try {
|
|
24
|
+
const checkCmd = process.platform === 'win32' ? `where ${cmd}` : `command -v ${cmd}`;
|
|
25
|
+
const res = execSync(checkCmd, { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
|
|
26
|
+
if (res) return cmd;
|
|
27
|
+
} catch (_) {}
|
|
28
|
+
}
|
|
29
|
+
return 'python3';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const pythonBin = findPython();
|
|
33
|
+
const args = ['-m', 'termux_stt', ...process.argv.slice(2)];
|
|
34
|
+
|
|
35
|
+
const child = spawn(pythonBin, args, {
|
|
36
|
+
stdio: 'inherit',
|
|
37
|
+
env: process.env
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
child.on('error', (err) => {
|
|
41
|
+
console.error(`[${'termux_stt'}] Failed to spawn python process (${pythonBin}):`, err.message);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
child.on('exit', (code, signal) => {
|
|
46
|
+
if (signal) {
|
|
47
|
+
process.kill(process.pid, signal);
|
|
48
|
+
} else {
|
|
49
|
+
process.exit(code || 0);
|
|
50
|
+
}
|
|
51
|
+
});
|
package/bin/termux-stt.js
CHANGED
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* AMEVA Standard Node.js CLI Runner for termux_stt.
|
|
4
|
-
* Automatically resolves Python 3 environment and dispatches to python -m termux_stt.
|
|
5
|
-
*/
|
|
6
|
-
const fs = require('fs');
|
|
7
|
-
const { spawn, execSync } = require('child_process');
|
|
8
|
-
|
|
9
|
-
function findPython() {
|
|
10
|
-
if (process.env.PYTHON && fs.existsSync(process.env.PYTHON)) {
|
|
11
|
-
return process.env.PYTHON;
|
|
12
|
-
}
|
|
13
|
-
const termuxBin = '/data/data/com.termux/files/usr/bin/python3';
|
|
14
|
-
if (fs.existsSync(termuxBin)) {
|
|
15
|
-
return termuxBin;
|
|
16
|
-
}
|
|
17
|
-
const termuxBinAlt = '/data/data/com.termux/files/usr/bin/python';
|
|
18
|
-
if (fs.existsSync(termuxBinAlt)) {
|
|
19
|
-
return termuxBinAlt;
|
|
20
|
-
}
|
|
21
|
-
const candidates = ['python3', 'python'];
|
|
22
|
-
for (const cmd of candidates) {
|
|
23
|
-
try {
|
|
24
|
-
const checkCmd = process.platform === 'win32' ? `where ${cmd}` : `command -v ${cmd}`;
|
|
25
|
-
const res = execSync(checkCmd, { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
|
|
26
|
-
if (res) return cmd;
|
|
27
|
-
} catch (_) {}
|
|
28
|
-
}
|
|
29
|
-
return 'python3';
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const pythonBin = findPython();
|
|
33
|
-
const args = ['-m', 'termux_stt.platform.installer', ...process.argv.slice(2)];
|
|
34
|
-
|
|
35
|
-
const child = spawn(pythonBin, args, {
|
|
36
|
-
stdio: 'inherit',
|
|
37
|
-
env: process.env
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
child.on('error', (err) => {
|
|
41
|
-
console.error(`[${'termux_stt'}] Failed to spawn python process (${pythonBin}):`, err.message);
|
|
42
|
-
process.exit(1);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
child.on('exit', (code, signal) => {
|
|
46
|
-
if (signal) {
|
|
47
|
-
process.kill(process.pid, signal);
|
|
48
|
-
} else {
|
|
49
|
-
process.exit(code || 0);
|
|
50
|
-
}
|
|
51
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* AMEVA Standard Node.js CLI Runner for termux_stt.
|
|
4
|
+
* Automatically resolves Python 3 environment and dispatches to python -m termux_stt.
|
|
5
|
+
*/
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const { spawn, execSync } = require('child_process');
|
|
8
|
+
|
|
9
|
+
function findPython() {
|
|
10
|
+
if (process.env.PYTHON && fs.existsSync(process.env.PYTHON)) {
|
|
11
|
+
return process.env.PYTHON;
|
|
12
|
+
}
|
|
13
|
+
const termuxBin = '/data/data/com.termux/files/usr/bin/python3';
|
|
14
|
+
if (fs.existsSync(termuxBin)) {
|
|
15
|
+
return termuxBin;
|
|
16
|
+
}
|
|
17
|
+
const termuxBinAlt = '/data/data/com.termux/files/usr/bin/python';
|
|
18
|
+
if (fs.existsSync(termuxBinAlt)) {
|
|
19
|
+
return termuxBinAlt;
|
|
20
|
+
}
|
|
21
|
+
const candidates = ['python3', 'python'];
|
|
22
|
+
for (const cmd of candidates) {
|
|
23
|
+
try {
|
|
24
|
+
const checkCmd = process.platform === 'win32' ? `where ${cmd}` : `command -v ${cmd}`;
|
|
25
|
+
const res = execSync(checkCmd, { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
|
|
26
|
+
if (res) return cmd;
|
|
27
|
+
} catch (_) {}
|
|
28
|
+
}
|
|
29
|
+
return 'python3';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const pythonBin = findPython();
|
|
33
|
+
const args = ['-m', 'termux_stt.platform.installer', ...process.argv.slice(2)];
|
|
34
|
+
|
|
35
|
+
const child = spawn(pythonBin, args, {
|
|
36
|
+
stdio: 'inherit',
|
|
37
|
+
env: process.env
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
child.on('error', (err) => {
|
|
41
|
+
console.error(`[${'termux_stt'}] Failed to spawn python process (${pythonBin}):`, err.message);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
child.on('exit', (code, signal) => {
|
|
46
|
+
if (signal) {
|
|
47
|
+
process.kill(process.pid, signal);
|
|
48
|
+
} else {
|
|
49
|
+
process.exit(code || 0);
|
|
50
|
+
}
|
|
51
|
+
});
|
package/index.d.ts
CHANGED
|
@@ -45,7 +45,7 @@ export class WhisperEngine extends Engine {
|
|
|
45
45
|
diarize(audioPath: string, numSpeakers?: number): Promise<TranscriptResult>;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
export class
|
|
48
|
+
export class SherpaEngine extends Engine {
|
|
49
49
|
transcribe(audioPath: string, options?: any): Promise<TranscriptResult>;
|
|
50
50
|
diarize(audioPath: string, numSpeakers?: number): Promise<TranscriptResult>;
|
|
51
51
|
}
|
|
@@ -56,8 +56,13 @@ export class HybridEngine extends Engine {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
export function createEngine(
|
|
59
|
-
engineName?: 'whisper' | '
|
|
59
|
+
engineName?: 'whisper' | 'sherpa' | 'hybrid' | string,
|
|
60
60
|
options?: EngineOptions
|
|
61
61
|
): Engine;
|
|
62
62
|
|
|
63
|
-
export
|
|
63
|
+
export class TermuxSTT {
|
|
64
|
+
engine: Engine;
|
|
65
|
+
constructor(options?: EngineOptions & { engine?: string });
|
|
66
|
+
transcribe(filePath: string, options?: any): Promise<TranscriptResult>;
|
|
67
|
+
diarize(filePath: string, options?: any): Promise<TranscriptResult>;
|
|
68
|
+
}
|
package/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* termux-stt Node.js entry point
|
|
2
|
+
* termux-stt Node.js entry point (v2.0.0)
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const { Engine, TranscriptResult, Segment, formatTime } = require('./lib/engine');
|
|
6
6
|
const { WhisperEngine } = require('./lib/whisper');
|
|
7
|
-
const {
|
|
7
|
+
const { SherpaEngine } = require('./lib/sherpa');
|
|
8
8
|
const { HybridEngine } = require('./lib/hybrid');
|
|
9
9
|
|
|
10
10
|
function createEngine(engineName = 'whisper', options = {}) {
|
|
@@ -12,12 +12,14 @@ function createEngine(engineName = 'whisper', options = {}) {
|
|
|
12
12
|
switch (name) {
|
|
13
13
|
case 'whisper':
|
|
14
14
|
return new WhisperEngine(options);
|
|
15
|
-
case '
|
|
16
|
-
return new
|
|
15
|
+
case 'sherpa':
|
|
16
|
+
return new SherpaEngine(options);
|
|
17
17
|
case 'hybrid':
|
|
18
18
|
return new HybridEngine(options);
|
|
19
|
+
case 'vosk':
|
|
20
|
+
throw new Error("Vosk has been deprecated and completely removed in v2.0.0. Please use 'sherpa' (SenseVoice) or 'whisper'.");
|
|
19
21
|
default:
|
|
20
|
-
throw new Error(`Unknown engine: ${engineName}. Available: whisper,
|
|
22
|
+
throw new Error(`Unknown engine: ${engineName}. Available: whisper, sherpa, hybrid`);
|
|
21
23
|
}
|
|
22
24
|
}
|
|
23
25
|
|
|
@@ -41,7 +43,7 @@ module.exports = {
|
|
|
41
43
|
TranscriptResult,
|
|
42
44
|
Segment,
|
|
43
45
|
WhisperEngine,
|
|
44
|
-
|
|
46
|
+
SherpaEngine,
|
|
45
47
|
HybridEngine,
|
|
46
48
|
formatTime
|
|
47
49
|
};
|
package/lib/hybrid.js
CHANGED
package/lib/sherpa.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SherpaEngine for Node.js - Non-Autoregressive SenseVoice STT & Neural Diarization
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { spawn } = require('child_process');
|
|
6
|
+
const { Engine, TranscriptResult, Segment } = require('./engine');
|
|
7
|
+
|
|
8
|
+
class SherpaEngine extends Engine {
|
|
9
|
+
constructor(config = {}) {
|
|
10
|
+
super(config);
|
|
11
|
+
this.model = config.model || 'sensevoice-small-int8';
|
|
12
|
+
this.lang = config.lang || 'ko';
|
|
13
|
+
this.threads = config.threads || 4;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async transcribe(audioPath, options = {}) {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
const args = [
|
|
19
|
+
'-m', 'termux_stt.cli.main',
|
|
20
|
+
'transcribe',
|
|
21
|
+
'--engine', 'sherpa',
|
|
22
|
+
'--model', this.model,
|
|
23
|
+
'--lang', this.lang,
|
|
24
|
+
'--format', 'json',
|
|
25
|
+
audioPath
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const proc = spawn('python', args, { env: process.env });
|
|
29
|
+
let stdout = '';
|
|
30
|
+
let stderr = '';
|
|
31
|
+
|
|
32
|
+
proc.stdout.on('data', (d) => { stdout += d.toString(); });
|
|
33
|
+
proc.stderr.on('data', (d) => { stderr += d.toString(); });
|
|
34
|
+
|
|
35
|
+
proc.on('close', (code) => {
|
|
36
|
+
if (code !== 0) {
|
|
37
|
+
return reject(new Error(`Sherpa transcription failed (code ${code}): ${stderr}`));
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const parsed = JSON.parse(stdout);
|
|
41
|
+
const segments = (parsed.segments || []).map(
|
|
42
|
+
s => new Segment(s.start, s.end, s.text, s.speaker, s.confidence)
|
|
43
|
+
);
|
|
44
|
+
resolve(new TranscriptResult(parsed.text, segments, parsed.language || this.lang, parsed.duration));
|
|
45
|
+
} catch (e) {
|
|
46
|
+
resolve(new TranscriptResult(stdout.trim(), [new Segment(0, 0, stdout.trim())], this.lang));
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async diarize(audioPath, numSpeakers = 2) {
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
const args = [
|
|
55
|
+
'-m', 'termux_stt.cli.main',
|
|
56
|
+
'diarize',
|
|
57
|
+
'--engine', 'sherpa',
|
|
58
|
+
'--speakers', String(numSpeakers),
|
|
59
|
+
'--format', 'json',
|
|
60
|
+
audioPath
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
const proc = spawn('python', args, { env: process.env });
|
|
64
|
+
let stdout = '';
|
|
65
|
+
let stderr = '';
|
|
66
|
+
|
|
67
|
+
proc.stdout.on('data', (d) => { stdout += d.toString(); });
|
|
68
|
+
proc.stderr.on('data', (d) => { stderr += d.toString(); });
|
|
69
|
+
|
|
70
|
+
proc.on('close', (code) => {
|
|
71
|
+
if (code !== 0) {
|
|
72
|
+
return reject(new Error(`Sherpa diarization failed (code ${code}): ${stderr}`));
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const parsed = JSON.parse(stdout);
|
|
76
|
+
const segments = (parsed.segments || []).map(
|
|
77
|
+
s => new Segment(s.start, s.end, s.text, s.speaker, s.confidence)
|
|
78
|
+
);
|
|
79
|
+
resolve(new TranscriptResult(parsed.text, segments, parsed.language || this.lang, parsed.duration));
|
|
80
|
+
} catch (e) {
|
|
81
|
+
resolve(new TranscriptResult(stdout.trim(), [new Segment(0, 0, stdout.trim())], this.lang));
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
getInfo() {
|
|
88
|
+
return {
|
|
89
|
+
name: 'Sherpa-ONNX (Node.js)',
|
|
90
|
+
model: this.model,
|
|
91
|
+
language: this.lang,
|
|
92
|
+
threads: this.threads
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = { SherpaEngine };
|
package/package.json
CHANGED
|
@@ -1,87 +1,87 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "termux-stt",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "On-device Speech-to-Text & speaker diarization framework
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"types": "index.d.ts",
|
|
7
|
-
"bin": {
|
|
8
|
-
"termux-stt": "bin/cli.js",
|
|
9
|
-
"termux-stt-install": "bin/termux-stt.js"
|
|
10
|
-
},
|
|
11
|
-
"files": [
|
|
12
|
-
"index.js",
|
|
13
|
-
"index.d.ts",
|
|
14
|
-
"lib",
|
|
15
|
-
"bin",
|
|
16
|
-
"README.md",
|
|
17
|
-
"LICENSE"
|
|
18
|
-
],
|
|
19
|
-
"repository": {
|
|
20
|
-
"type": "git",
|
|
21
|
-
"url": "git+https://github.com/uno-km/termux-stt.git"
|
|
22
|
-
},
|
|
23
|
-
"keywords": [
|
|
24
|
-
"termux",
|
|
25
|
-
"stt",
|
|
26
|
-
"speech-to-text",
|
|
27
|
-
"whisper",
|
|
28
|
-
"whisper-cpp",
|
|
29
|
-
"vosk",
|
|
30
|
-
"sherpa-onnx",
|
|
31
|
-
"diarization",
|
|
32
|
-
"speaker-diarization",
|
|
33
|
-
"voice-recognition",
|
|
34
|
-
"audio-transcription",
|
|
35
|
-
"on-device-ai",
|
|
36
|
-
"edge-ai",
|
|
37
|
-
"mobile-ai",
|
|
38
|
-
"vulkan",
|
|
39
|
-
"vulkan-compute",
|
|
40
|
-
"gpu-acceleration",
|
|
41
|
-
"real-time-factor",
|
|
42
|
-
"low-latency",
|
|
43
|
-
"arm64",
|
|
44
|
-
"android",
|
|
45
|
-
"snapdragon",
|
|
46
|
-
"adreno",
|
|
47
|
-
"exynos",
|
|
48
|
-
"arm-mali",
|
|
49
|
-
"zero-compilation",
|
|
50
|
-
"vad",
|
|
51
|
-
"voice-activity-detection",
|
|
52
|
-
"silero-vad",
|
|
53
|
-
"x-vector",
|
|
54
|
-
"k-means",
|
|
55
|
-
"clustering",
|
|
56
|
-
"srt-export",
|
|
57
|
-
"vtt-export",
|
|
58
|
-
"rttm",
|
|
59
|
-
"microphone-streaming",
|
|
60
|
-
"headless-audio",
|
|
61
|
-
"pulseaudio",
|
|
62
|
-
"offline-speech",
|
|
63
|
-
"privacy-first",
|
|
64
|
-
"termux-aichain",
|
|
65
|
-
"termux-tts",
|
|
66
|
-
"termux-llamacpp",
|
|
67
|
-
"termux-diffusion",
|
|
68
|
-
"ameva-runtime",
|
|
69
|
-
"ggml",
|
|
70
|
-
"quantization",
|
|
71
|
-
"bionic-libc",
|
|
72
|
-
"autonomous-agents",
|
|
73
|
-
"voice-assistant"
|
|
74
|
-
],
|
|
75
|
-
"author": "uno-km (AMEVA Foundation) <dev@ameva.org>",
|
|
76
|
-
"license": "MIT",
|
|
77
|
-
"bugs": {
|
|
78
|
-
"url": "https://github.com/uno-km/termux-stt/issues"
|
|
79
|
-
},
|
|
80
|
-
"homepage": "https://uno-km.github.io/termux-stt/",
|
|
81
|
-
"dependencies": {
|
|
82
|
-
"@ameva/runtime": ">=2.0.0"
|
|
83
|
-
},
|
|
84
|
-
"engines": {
|
|
85
|
-
"node": ">=16.0.0"
|
|
86
|
-
}
|
|
87
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "termux-stt",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "On-device Speech-to-Text & neural speaker diarization framework for Android Termux (Whisper Vulkan GPU & Sherpa-ONNX)",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"termux-stt": "bin/cli.js",
|
|
9
|
+
"termux-stt-install": "bin/termux-stt.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"index.js",
|
|
13
|
+
"index.d.ts",
|
|
14
|
+
"lib",
|
|
15
|
+
"bin",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/uno-km/termux-stt.git"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"termux",
|
|
25
|
+
"stt",
|
|
26
|
+
"speech-to-text",
|
|
27
|
+
"whisper",
|
|
28
|
+
"whisper-cpp",
|
|
29
|
+
"vosk",
|
|
30
|
+
"sherpa-onnx",
|
|
31
|
+
"diarization",
|
|
32
|
+
"speaker-diarization",
|
|
33
|
+
"voice-recognition",
|
|
34
|
+
"audio-transcription",
|
|
35
|
+
"on-device-ai",
|
|
36
|
+
"edge-ai",
|
|
37
|
+
"mobile-ai",
|
|
38
|
+
"vulkan",
|
|
39
|
+
"vulkan-compute",
|
|
40
|
+
"gpu-acceleration",
|
|
41
|
+
"real-time-factor",
|
|
42
|
+
"low-latency",
|
|
43
|
+
"arm64",
|
|
44
|
+
"android",
|
|
45
|
+
"snapdragon",
|
|
46
|
+
"adreno",
|
|
47
|
+
"exynos",
|
|
48
|
+
"arm-mali",
|
|
49
|
+
"zero-compilation",
|
|
50
|
+
"vad",
|
|
51
|
+
"voice-activity-detection",
|
|
52
|
+
"silero-vad",
|
|
53
|
+
"x-vector",
|
|
54
|
+
"k-means",
|
|
55
|
+
"clustering",
|
|
56
|
+
"srt-export",
|
|
57
|
+
"vtt-export",
|
|
58
|
+
"rttm",
|
|
59
|
+
"microphone-streaming",
|
|
60
|
+
"headless-audio",
|
|
61
|
+
"pulseaudio",
|
|
62
|
+
"offline-speech",
|
|
63
|
+
"privacy-first",
|
|
64
|
+
"termux-aichain",
|
|
65
|
+
"termux-tts",
|
|
66
|
+
"termux-llamacpp",
|
|
67
|
+
"termux-diffusion",
|
|
68
|
+
"ameva-runtime",
|
|
69
|
+
"ggml",
|
|
70
|
+
"quantization",
|
|
71
|
+
"bionic-libc",
|
|
72
|
+
"autonomous-agents",
|
|
73
|
+
"voice-assistant"
|
|
74
|
+
],
|
|
75
|
+
"author": "uno-km (AMEVA Foundation) <dev@ameva.org>",
|
|
76
|
+
"license": "MIT",
|
|
77
|
+
"bugs": {
|
|
78
|
+
"url": "https://github.com/uno-km/termux-stt/issues"
|
|
79
|
+
},
|
|
80
|
+
"homepage": "https://uno-km.github.io/termux-stt/",
|
|
81
|
+
"dependencies": {
|
|
82
|
+
"@ameva/runtime": ">=2.0.0"
|
|
83
|
+
},
|
|
84
|
+
"engines": {
|
|
85
|
+
"node": ">=16.0.0"
|
|
86
|
+
}
|
|
87
|
+
}
|
package/lib/vosk.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* VoskEngine for Node.js
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const { spawn } = require('child_process');
|
|
6
|
-
const { Engine, TranscriptResult, Segment } = require('./engine');
|
|
7
|
-
|
|
8
|
-
class VoskEngine extends Engine {
|
|
9
|
-
constructor(config = {}) {
|
|
10
|
-
super(config);
|
|
11
|
-
this.model = config.model || 'small-ko-0.22';
|
|
12
|
-
this.lang = config.lang || 'ko';
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
async transcribe(audioPath, options = {}) {
|
|
16
|
-
return new Promise((resolve, reject) => {
|
|
17
|
-
const args = [
|
|
18
|
-
'-m', 'termux_stt.cli.main',
|
|
19
|
-
'transcribe',
|
|
20
|
-
'--engine', 'vosk',
|
|
21
|
-
'--model', this.model,
|
|
22
|
-
'--lang', this.lang,
|
|
23
|
-
'--format', 'json',
|
|
24
|
-
audioPath
|
|
25
|
-
];
|
|
26
|
-
|
|
27
|
-
const proc = spawn('python', args, { env: process.env });
|
|
28
|
-
let stdout = '';
|
|
29
|
-
let stderr = '';
|
|
30
|
-
|
|
31
|
-
proc.stdout.on('data', (d) => { stdout += d.toString(); });
|
|
32
|
-
proc.stderr.on('data', (d) => { stderr += d.toString(); });
|
|
33
|
-
|
|
34
|
-
proc.on('close', (code) => {
|
|
35
|
-
if (code !== 0) {
|
|
36
|
-
return reject(new Error(`Vosk transcription failed (code ${code}): ${stderr}`));
|
|
37
|
-
}
|
|
38
|
-
try {
|
|
39
|
-
const parsed = JSON.parse(stdout);
|
|
40
|
-
const segments = (parsed.segments || []).map(
|
|
41
|
-
s => new Segment(s.start, s.end, s.text, s.speaker, s.confidence)
|
|
42
|
-
);
|
|
43
|
-
resolve(new TranscriptResult(parsed.text, segments, parsed.language || this.lang, parsed.duration));
|
|
44
|
-
} catch (e) {
|
|
45
|
-
resolve(new TranscriptResult(stdout.trim(), [new Segment(0, 0, stdout.trim())], this.lang));
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
getInfo() {
|
|
52
|
-
return {
|
|
53
|
-
name: 'Vosk (Node.js)',
|
|
54
|
-
model: this.model,
|
|
55
|
-
language: this.lang
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
module.exports = { VoskEngine };
|