termux-stt 2.0.4 → 2.1.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/bin/termux-stt.js CHANGED
@@ -1,51 +1,55 @@
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
+ * Copyright (c) 2026 Uno Kim / AMEVA Foundation. All rights reserved.
4
+ * SPDX-License-Identifier: Apache-2.0 OR Commercial-License
5
+ */
6
+ /**
7
+ * AMEVA Standard Node.js CLI Runner for termux_stt.
8
+ * Automatically resolves Python 3 environment and dispatches to python -m termux_stt.
9
+ */
10
+ const fs = require('fs');
11
+ const { spawn, execSync } = require('child_process');
12
+
13
+ function findPython() {
14
+ if (process.env.PYTHON && fs.existsSync(process.env.PYTHON)) {
15
+ return process.env.PYTHON;
16
+ }
17
+ const termuxBin = '/data/data/com.termux/files/usr/bin/python3';
18
+ if (fs.existsSync(termuxBin)) {
19
+ return termuxBin;
20
+ }
21
+ const termuxBinAlt = '/data/data/com.termux/files/usr/bin/python';
22
+ if (fs.existsSync(termuxBinAlt)) {
23
+ return termuxBinAlt;
24
+ }
25
+ const candidates = ['python3', 'python'];
26
+ for (const cmd of candidates) {
27
+ try {
28
+ const checkCmd = process.platform === 'win32' ? `where ${cmd}` : `command -v ${cmd}`;
29
+ const res = execSync(checkCmd, { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
30
+ if (res) return cmd;
31
+ } catch (_) {}
32
+ }
33
+ return 'python3';
34
+ }
35
+
36
+ const pythonBin = findPython();
37
+ const args = ['-m', 'termux_stt.platform.installer', ...process.argv.slice(2)];
38
+
39
+ const child = spawn(pythonBin, args, {
40
+ stdio: 'inherit',
41
+ env: process.env
42
+ });
43
+
44
+ child.on('error', (err) => {
45
+ console.error(`[${'termux_stt'}] Failed to spawn python process (${pythonBin}):`, err.message);
46
+ process.exit(1);
47
+ });
48
+
49
+ child.on('exit', (code, signal) => {
50
+ if (signal) {
51
+ process.kill(process.pid, signal);
52
+ } else {
53
+ process.exit(code || 0);
54
+ }
55
+ });
package/index.d.ts CHANGED
@@ -1,68 +1,72 @@
1
- export interface EngineOptions {
2
- model?: string;
3
- lang?: string;
4
- device?: 'auto' | 'vulkan' | 'gpu' | 'cpu';
5
- threads?: number;
6
- vad?: boolean;
7
- vadThreshold?: number;
8
- quantization?: string;
9
- numSpeakers?: number;
10
- customModelPath?: string;
11
- [key: string]: any;
12
- }
13
-
14
- export interface Segment {
15
- start: number;
16
- end: number;
17
- text: string;
18
- speaker?: string;
19
- confidence?: number;
20
- }
21
-
22
- export class TranscriptResult {
23
- text: string;
24
- segments: Segment[];
25
- language?: string;
26
- duration?: number;
27
- speakers?: string[];
28
-
29
- toJson(): string;
30
- toSrt(): string;
31
- toVtt(): string;
32
- toRttm(fileId?: string): string;
33
- }
34
-
35
- export abstract class Engine {
36
- options: EngineOptions;
37
- constructor(options?: EngineOptions);
38
- abstract transcribe(audioPath: string, options?: any): Promise<TranscriptResult>;
39
- abstract diarize(audioPath: string, numSpeakers?: number): Promise<TranscriptResult>;
40
- getInfo(): Record<string, any>;
41
- }
42
-
43
- export class WhisperEngine extends Engine {
44
- transcribe(audioPath: string, options?: any): Promise<TranscriptResult>;
45
- diarize(audioPath: string, numSpeakers?: number): Promise<TranscriptResult>;
46
- }
47
-
48
- export class SherpaEngine extends Engine {
49
- transcribe(audioPath: string, options?: any): Promise<TranscriptResult>;
50
- diarize(audioPath: string, numSpeakers?: number): Promise<TranscriptResult>;
51
- }
52
-
53
- export class HybridEngine extends Engine {
54
- transcribe(audioPath: string, options?: any): Promise<TranscriptResult>;
55
- diarize(audioPath: string, numSpeakers?: number): Promise<TranscriptResult>;
56
- }
57
-
58
- export function createEngine(
59
- engineName?: 'whisper' | 'sherpa' | 'hybrid' | string,
60
- options?: EngineOptions
61
- ): Engine;
62
-
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
- }
1
+ /**
2
+ * Copyright (c) 2026 Uno Kim / AMEVA Foundation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0 OR Commercial-License
4
+ */
5
+ export interface EngineOptions {
6
+ model?: string;
7
+ lang?: string;
8
+ device?: 'auto' | 'vulkan' | 'gpu' | 'cpu';
9
+ threads?: number;
10
+ vad?: boolean;
11
+ vadThreshold?: number;
12
+ quantization?: string;
13
+ numSpeakers?: number;
14
+ customModelPath?: string;
15
+ [key: string]: any;
16
+ }
17
+
18
+ export interface Segment {
19
+ start: number;
20
+ end: number;
21
+ text: string;
22
+ speaker?: string;
23
+ confidence?: number;
24
+ }
25
+
26
+ export class TranscriptResult {
27
+ text: string;
28
+ segments: Segment[];
29
+ language?: string;
30
+ duration?: number;
31
+ speakers?: string[];
32
+
33
+ toJson(): string;
34
+ toSrt(): string;
35
+ toVtt(): string;
36
+ toRttm(fileId?: string): string;
37
+ }
38
+
39
+ export abstract class Engine {
40
+ options: EngineOptions;
41
+ constructor(options?: EngineOptions);
42
+ abstract transcribe(audioPath: string, options?: any): Promise<TranscriptResult>;
43
+ abstract diarize(audioPath: string, numSpeakers?: number): Promise<TranscriptResult>;
44
+ getInfo(): Record<string, any>;
45
+ }
46
+
47
+ export class WhisperEngine extends Engine {
48
+ transcribe(audioPath: string, options?: any): Promise<TranscriptResult>;
49
+ diarize(audioPath: string, numSpeakers?: number): Promise<TranscriptResult>;
50
+ }
51
+
52
+ export class SherpaEngine extends Engine {
53
+ transcribe(audioPath: string, options?: any): Promise<TranscriptResult>;
54
+ diarize(audioPath: string, numSpeakers?: number): Promise<TranscriptResult>;
55
+ }
56
+
57
+ export class HybridEngine extends Engine {
58
+ transcribe(audioPath: string, options?: any): Promise<TranscriptResult>;
59
+ diarize(audioPath: string, numSpeakers?: number): Promise<TranscriptResult>;
60
+ }
61
+
62
+ export function createEngine(
63
+ engineName?: 'whisper' | 'sherpa' | 'hybrid' | string,
64
+ options?: EngineOptions
65
+ ): Engine;
66
+
67
+ export class TermuxSTT {
68
+ engine: Engine;
69
+ constructor(options?: EngineOptions & { engine?: string });
70
+ transcribe(filePath: string, options?: any): Promise<TranscriptResult>;
71
+ diarize(filePath: string, options?: any): Promise<TranscriptResult>;
72
+ }
package/index.js CHANGED
@@ -1,49 +1,53 @@
1
- /**
2
- * termux-stt Node.js entry point (v2.0.0)
3
- */
4
-
5
- const { Engine, TranscriptResult, Segment, formatTime } = require('./lib/engine');
6
- const { WhisperEngine } = require('./lib/whisper');
7
- const { SherpaEngine } = require('./lib/sherpa');
8
- const { HybridEngine } = require('./lib/hybrid');
9
-
10
- function createEngine(engineName = 'whisper', options = {}) {
11
- const name = String(engineName).toLowerCase();
12
- switch (name) {
13
- case 'whisper':
14
- return new WhisperEngine(options);
15
- case 'sherpa':
16
- return new SherpaEngine(options);
17
- case 'hybrid':
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'.");
21
- default:
22
- throw new Error(`Unknown engine: ${engineName}. Available: whisper, sherpa, hybrid`);
23
- }
24
- }
25
-
26
- class TermuxSTT {
27
- constructor(options = {}) {
28
- const engineName = options.engine || 'whisper';
29
- this.engine = createEngine(engineName, options);
30
- }
31
- transcribe(filePath, options = {}) {
32
- return this.engine.transcribe(filePath, options);
33
- }
34
- diarize(filePath, options = {}) {
35
- return this.engine.diarize(filePath, options);
36
- }
37
- }
38
-
39
- module.exports = {
40
- TermuxSTT,
41
- createEngine,
42
- Engine,
43
- TranscriptResult,
44
- Segment,
45
- WhisperEngine,
46
- SherpaEngine,
47
- HybridEngine,
48
- formatTime
49
- };
1
+ /**
2
+ * Copyright (c) 2026 Uno Kim / AMEVA Foundation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0 OR Commercial-License
4
+ */
5
+ /**
6
+ * termux-stt Node.js entry point (v2.0.0)
7
+ */
8
+
9
+ const { Engine, TranscriptResult, Segment, formatTime } = require('./lib/engine');
10
+ const { WhisperEngine } = require('./lib/whisper');
11
+ const { SherpaEngine } = require('./lib/sherpa');
12
+ const { HybridEngine } = require('./lib/hybrid');
13
+
14
+ function createEngine(engineName = 'whisper', options = {}) {
15
+ const name = String(engineName).toLowerCase();
16
+ switch (name) {
17
+ case 'whisper':
18
+ return new WhisperEngine(options);
19
+ case 'sherpa':
20
+ return new SherpaEngine(options);
21
+ case 'hybrid':
22
+ return new HybridEngine(options);
23
+ case 'vosk':
24
+ throw new Error("Vosk has been deprecated and completely removed in v2.0.0. Please use 'sherpa' (SenseVoice) or 'whisper'.");
25
+ default:
26
+ throw new Error(`Unknown engine: ${engineName}. Available: whisper, sherpa, hybrid`);
27
+ }
28
+ }
29
+
30
+ class TermuxSTT {
31
+ constructor(options = {}) {
32
+ const engineName = options.engine || 'whisper';
33
+ this.engine = createEngine(engineName, options);
34
+ }
35
+ transcribe(filePath, options = {}) {
36
+ return this.engine.transcribe(filePath, options);
37
+ }
38
+ diarize(filePath, options = {}) {
39
+ return this.engine.diarize(filePath, options);
40
+ }
41
+ }
42
+
43
+ module.exports = {
44
+ TermuxSTT,
45
+ createEngine,
46
+ Engine,
47
+ TranscriptResult,
48
+ Segment,
49
+ WhisperEngine,
50
+ SherpaEngine,
51
+ HybridEngine,
52
+ formatTime
53
+ };
package/lib/engine.js CHANGED
@@ -1,92 +1,96 @@
1
- /**
2
- * termux-stt - Engine Abstraction for Node.js
3
- */
4
-
5
- class TranscriptResult {
6
- constructor(text, segments = [], language = 'ko', duration = 0) {
7
- this.text = text;
8
- this.segments = segments;
9
- this.language = language;
10
- this.duration = duration;
11
- }
12
-
13
- toJson() {
14
- return JSON.stringify(this, null, 2);
15
- }
16
-
17
- toSrt() {
18
- return this.segments.map((seg, i) => {
19
- const start = formatTime(seg.start);
20
- const end = formatTime(seg.end);
21
- return `${i + 1}\n${start} --> ${end}\n${seg.text}\n`;
22
- }).join('\n');
23
- }
24
-
25
- toVtt() {
26
- const body = this.segments.map((seg) => {
27
- const start = formatTime(seg.start, true);
28
- const end = formatTime(seg.end, true);
29
- return `${start} --> ${end}\n${seg.text}\n`;
30
- }).join('\n');
31
- return `WEBVTT\n\n${body}`;
32
- }
33
-
34
- toRttm(fileId = 'audio') {
35
- return this.segments.map((seg) => {
36
- const spk = seg.speaker || 'Speaker_0';
37
- const dur = (seg.end - seg.start).toFixed(2);
38
- return `SPEAKER ${fileId} 1 ${seg.start.toFixed(2)} ${dur} <NA> <NA> ${spk} <NA> <NA>`;
39
- }).join('\n');
40
- }
41
- }
42
-
43
- class Segment {
44
- constructor(start, end, text, speaker = null, confidence = null) {
45
- this.start = start;
46
- this.end = end;
47
- this.text = text;
48
- this.speaker = speaker;
49
- this.confidence = confidence;
50
- }
51
- }
52
-
53
- function formatTime(seconds, isVtt = false) {
54
- const s = Math.max(0, seconds || 0);
55
- const hrs = Math.floor(s / 3600);
56
- const mins = Math.floor((s % 3600) / 60);
57
- const secs = Math.floor(s % 60);
58
- const ms = Math.floor((s % 1) * 1000);
59
-
60
- const pad = (n, z = 2) => String(n).padStart(z, '0');
61
- const sep = isVtt ? '.' : ',';
62
- return `${pad(hrs)}:${pad(mins)}:${pad(secs)}${sep}${pad(ms, 3)}`;
63
- }
64
-
65
- class Engine {
66
- constructor(config = {}) {
67
- this.config = config;
68
- }
69
-
70
- async transcribe(audioPath, options = {}) {
71
- throw new Error('transcribe() must be implemented by subclass');
72
- }
73
-
74
- async diarize(audioPath, numSpeakers = 2) {
75
- throw new Error('diarize() must be implemented by subclass');
76
- }
77
-
78
- streamMic(duration = null) {
79
- throw new Error('streamMic() must be implemented by subclass');
80
- }
81
-
82
- getInfo() {
83
- return { name: this.constructor.name, config: this.config };
84
- }
85
- }
86
-
87
- module.exports = {
88
- Engine,
89
- TranscriptResult,
90
- Segment,
91
- formatTime
92
- };
1
+ /**
2
+ * Copyright (c) 2026 Uno Kim / AMEVA Foundation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0 OR Commercial-License
4
+ */
5
+ /**
6
+ * termux-stt - Engine Abstraction for Node.js
7
+ */
8
+
9
+ class TranscriptResult {
10
+ constructor(text, segments = [], language = 'ko', duration = 0) {
11
+ this.text = text;
12
+ this.segments = segments;
13
+ this.language = language;
14
+ this.duration = duration;
15
+ }
16
+
17
+ toJson() {
18
+ return JSON.stringify(this, null, 2);
19
+ }
20
+
21
+ toSrt() {
22
+ return this.segments.map((seg, i) => {
23
+ const start = formatTime(seg.start);
24
+ const end = formatTime(seg.end);
25
+ return `${i + 1}\n${start} --> ${end}\n${seg.text}\n`;
26
+ }).join('\n');
27
+ }
28
+
29
+ toVtt() {
30
+ const body = this.segments.map((seg) => {
31
+ const start = formatTime(seg.start, true);
32
+ const end = formatTime(seg.end, true);
33
+ return `${start} --> ${end}\n${seg.text}\n`;
34
+ }).join('\n');
35
+ return `WEBVTT\n\n${body}`;
36
+ }
37
+
38
+ toRttm(fileId = 'audio') {
39
+ return this.segments.map((seg) => {
40
+ const spk = seg.speaker || 'Speaker_0';
41
+ const dur = (seg.end - seg.start).toFixed(2);
42
+ return `SPEAKER ${fileId} 1 ${seg.start.toFixed(2)} ${dur} <NA> <NA> ${spk} <NA> <NA>`;
43
+ }).join('\n');
44
+ }
45
+ }
46
+
47
+ class Segment {
48
+ constructor(start, end, text, speaker = null, confidence = null) {
49
+ this.start = start;
50
+ this.end = end;
51
+ this.text = text;
52
+ this.speaker = speaker;
53
+ this.confidence = confidence;
54
+ }
55
+ }
56
+
57
+ function formatTime(seconds, isVtt = false) {
58
+ const s = Math.max(0, seconds || 0);
59
+ const hrs = Math.floor(s / 3600);
60
+ const mins = Math.floor((s % 3600) / 60);
61
+ const secs = Math.floor(s % 60);
62
+ const ms = Math.floor((s % 1) * 1000);
63
+
64
+ const pad = (n, z = 2) => String(n).padStart(z, '0');
65
+ const sep = isVtt ? '.' : ',';
66
+ return `${pad(hrs)}:${pad(mins)}:${pad(secs)}${sep}${pad(ms, 3)}`;
67
+ }
68
+
69
+ class Engine {
70
+ constructor(config = {}) {
71
+ this.config = config;
72
+ }
73
+
74
+ async transcribe(audioPath, options = {}) {
75
+ throw new Error('transcribe() must be implemented by subclass');
76
+ }
77
+
78
+ async diarize(audioPath, numSpeakers = 2) {
79
+ throw new Error('diarize() must be implemented by subclass');
80
+ }
81
+
82
+ streamMic(duration = null) {
83
+ throw new Error('streamMic() must be implemented by subclass');
84
+ }
85
+
86
+ getInfo() {
87
+ return { name: this.constructor.name, config: this.config };
88
+ }
89
+ }
90
+
91
+ module.exports = {
92
+ Engine,
93
+ TranscriptResult,
94
+ Segment,
95
+ formatTime
96
+ };