viet-asr 0.1.0-dev.3
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 +106 -0
- package/bin/vietasr.js +55 -0
- package/index.js +12 -0
- package/lib/native-download.js +77 -0
- package/lib/native.js +115 -0
- package/lib/pipeline.js +122 -0
- package/lib/result.js +44 -0
- package/lib/session.js +55 -0
- package/package.json +45 -0
- package/scripts/install.js +15 -0
package/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# vietasr — Node.js binding
|
|
2
|
+
|
|
3
|
+
Offline Vietnamese Speech AI SDK for Node.js. Pure FFI over the same C/C++ core that powers every other vietasr binding.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install vietasr
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The package bundles a prebuilt `libvietasr.{so,dylib,dll}` + `libonnxruntime.{so,dylib,dll}` for your platform under `_native/`. No system dependencies.
|
|
12
|
+
|
|
13
|
+
## Quickstart
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
const vietasr = require("vietasr");
|
|
17
|
+
|
|
18
|
+
const pipe = vietasr.Pipeline.preset("transcribe");
|
|
19
|
+
const result = pipe.transcribe("audio.wav");
|
|
20
|
+
console.log(result.text);
|
|
21
|
+
pipe.close();
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Streaming
|
|
25
|
+
|
|
26
|
+
```js
|
|
27
|
+
const vietasr = require("vietasr");
|
|
28
|
+
|
|
29
|
+
const pipe = vietasr.Pipeline.preset("transcribe");
|
|
30
|
+
const session = pipe.stream(16000);
|
|
31
|
+
|
|
32
|
+
for (const chunk of micChunks()) {
|
|
33
|
+
session.accept(chunk); // Int16Array, Float32Array, or Buffer
|
|
34
|
+
process.stdout.write(`\r${session.partial().text}`);
|
|
35
|
+
}
|
|
36
|
+
console.log(`\n${session.final().text}`);
|
|
37
|
+
session.close();
|
|
38
|
+
pipe.close();
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Custom pipeline
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
const vietasr = require("vietasr");
|
|
45
|
+
|
|
46
|
+
const pipe = vietasr.Pipeline.new()
|
|
47
|
+
.add("vad")
|
|
48
|
+
.add("vietasr")
|
|
49
|
+
.add("punctuation")
|
|
50
|
+
.add("gender")
|
|
51
|
+
.add("emotion")
|
|
52
|
+
.build();
|
|
53
|
+
|
|
54
|
+
const result = pipe.transcribe("call.wav");
|
|
55
|
+
console.log(result.text);
|
|
56
|
+
console.log(result.field("gender"));
|
|
57
|
+
console.log(result.field("emotion"));
|
|
58
|
+
pipe.close();
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## CLI
|
|
62
|
+
|
|
63
|
+
The package installs a `vietasr` binary:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
vietasr audio.wav
|
|
67
|
+
vietasr --preset analytics call.wav --pretty
|
|
68
|
+
vietasr --module vad --module vietasr --module gender audio.wav
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## API
|
|
72
|
+
|
|
73
|
+
| Object | Method | Returns |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| `Pipeline` | `.preset(name)` | `Pipeline` |
|
|
76
|
+
| `Pipeline` | `.new()` | `Pipeline` |
|
|
77
|
+
| `Pipeline` | `.add(module, config?)` | `Pipeline` |
|
|
78
|
+
| `Pipeline` | `.setBackend("auto" \| "onnx" \| "coreml")` | `Pipeline` |
|
|
79
|
+
| `Pipeline` | `.setModelDir(path)` | `Pipeline` |
|
|
80
|
+
| `Pipeline` | `.build()` | `Pipeline` |
|
|
81
|
+
| `Pipeline` | `.transcribe(filePathOrInt16Array, sampleRate?)` | `Result` |
|
|
82
|
+
| `Pipeline` | `.stream(sampleRate)` | `Session` |
|
|
83
|
+
| `Pipeline` | `.close()` | void |
|
|
84
|
+
| `Session` | `.accept(Int16Array \| Float32Array \| Buffer)` | bool (true = endpoint reached) |
|
|
85
|
+
| `Session` | `.partial()` / `.result()` / `.final()` | `Result` |
|
|
86
|
+
| `Session` | `.reset()` / `.close()` | void |
|
|
87
|
+
| `Result` | `.text`, `.partial`, `.isFinal`, `.segments`, `.speakers` | typed |
|
|
88
|
+
| `Result` | `.field(key)` | any (extra JSON keys) |
|
|
89
|
+
| `Result` | `.toJson()` | string |
|
|
90
|
+
| module-level | `vietasr.listModules()`, `listPresets()`, `version()`, `setLogLevel(level)` | — |
|
|
91
|
+
|
|
92
|
+
## Thread safety
|
|
93
|
+
|
|
94
|
+
- One `Pipeline` can serve many concurrent `Session`s — each Session has its own state.
|
|
95
|
+
- For pure parallelism, prefer multiple Sessions on a shared Pipeline (engine loaded once).
|
|
96
|
+
- See [docs/thread-safety.md](../../docs/thread-safety.md) for the full guide.
|
|
97
|
+
|
|
98
|
+
## Audio format
|
|
99
|
+
|
|
100
|
+
The SDK accepts any sample rate and either mono or stereo (auto-converted internally). For best accuracy, feed 16 kHz mono 16-bit PCM directly.
|
|
101
|
+
|
|
102
|
+
## Models
|
|
103
|
+
|
|
104
|
+
The vietasr model is **bundled inside the SDK** — no download, no network. `model.onnx`
|
|
105
|
+
is committed to the repo as <50 MB chunks and baked into the native `libvietasr` that
|
|
106
|
+
ships with this package.
|
package/bin/vietasr.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const vietasr = require("..");
|
|
3
|
+
|
|
4
|
+
function main() {
|
|
5
|
+
const argv = process.argv.slice(2);
|
|
6
|
+
let preset = "transcribe";
|
|
7
|
+
let pretty = false;
|
|
8
|
+
let wavPath = null;
|
|
9
|
+
const modules = [];
|
|
10
|
+
|
|
11
|
+
for (let i = 0; i < argv.length; ++i) {
|
|
12
|
+
const arg = argv[i];
|
|
13
|
+
if (arg === "--preset" && i + 1 < argv.length) {
|
|
14
|
+
preset = argv[++i];
|
|
15
|
+
} else if (arg === "--module" && i + 1 < argv.length) {
|
|
16
|
+
modules.push(argv[++i]);
|
|
17
|
+
} else if (arg === "--pretty") {
|
|
18
|
+
pretty = true;
|
|
19
|
+
} else if (arg === "-h" || arg === "--help") {
|
|
20
|
+
console.log(`Usage: vietasr [--preset NAME] [--module NAME]... [--pretty] <wav-file>`);
|
|
21
|
+
console.log(`\nAvailable presets: ${JSON.stringify(vietasr.listPresets())}`);
|
|
22
|
+
console.log(`Available modules: ${JSON.stringify(vietasr.listModules())}`);
|
|
23
|
+
process.exit(0);
|
|
24
|
+
} else if (!arg.startsWith("-")) {
|
|
25
|
+
wavPath = arg;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!wavPath) {
|
|
30
|
+
console.error("missing WAV path; pass --help for usage");
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let pipe;
|
|
35
|
+
if (modules.length) {
|
|
36
|
+
pipe = vietasr.Pipeline.new();
|
|
37
|
+
for (const m of modules) pipe.add(m);
|
|
38
|
+
pipe.build();
|
|
39
|
+
} else {
|
|
40
|
+
pipe = vietasr.Pipeline.preset(preset);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const result = pipe.transcribe(wavPath);
|
|
45
|
+
if (pretty) {
|
|
46
|
+
console.log(JSON.stringify(result.payload, null, 2));
|
|
47
|
+
} else {
|
|
48
|
+
console.log(result.toJson());
|
|
49
|
+
}
|
|
50
|
+
} finally {
|
|
51
|
+
pipe.close();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
main();
|
package/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const { Pipeline, PipelineError, Session, Result } = require("./lib/pipeline");
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
Pipeline,
|
|
5
|
+
PipelineError,
|
|
6
|
+
Session,
|
|
7
|
+
Result,
|
|
8
|
+
version: () => Pipeline.version(),
|
|
9
|
+
listModules: () => Pipeline.listModules(),
|
|
10
|
+
listPresets: () => Pipeline.listPresets(),
|
|
11
|
+
setLogLevel: (level) => Pipeline.setLogLevel(level),
|
|
12
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// Resolves the libvietasr native library. The npm package ships small; the
|
|
4
|
+
// platform-specific native (~67 MB, ONNX model embedded) is downloaded from the
|
|
5
|
+
// matching GitHub Release into a per-user cache on install / first use.
|
|
6
|
+
|
|
7
|
+
const fs = require("fs");
|
|
8
|
+
const os = require("os");
|
|
9
|
+
const path = require("path");
|
|
10
|
+
const { execFileSync } = require("child_process");
|
|
11
|
+
|
|
12
|
+
const REPO = "dangvansam/viet-asr";
|
|
13
|
+
|
|
14
|
+
function platformKey() {
|
|
15
|
+
if (process.platform === "win32") return "win-x64";
|
|
16
|
+
if (process.platform === "darwin") return "darwin-universal2";
|
|
17
|
+
if (process.platform === "linux") {
|
|
18
|
+
if (process.arch === "x64") return "linux-x64";
|
|
19
|
+
if (process.arch === "arm64") return "linux-arm64";
|
|
20
|
+
}
|
|
21
|
+
throw new Error(
|
|
22
|
+
`viet-asr: unsupported platform ${process.platform}/${process.arch}`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function libFileName() {
|
|
27
|
+
if (process.platform === "win32") return "vietasr.dll";
|
|
28
|
+
if (process.platform === "darwin") return "libvietasr.dylib";
|
|
29
|
+
return "libvietasr.so";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function cacheDir(version) {
|
|
33
|
+
const base =
|
|
34
|
+
process.env.XDG_CACHE_HOME || path.join(os.homedir(), ".cache");
|
|
35
|
+
return path.join(base, "viet-asr", version);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Downloads + extracts the native bundle into the per-user cache. Synchronous
|
|
39
|
+
// (shells out to curl + tar, present on every supported OS) so it works both
|
|
40
|
+
// from the postinstall hook and as a lazy fallback inside require().
|
|
41
|
+
function ensureNative(version) {
|
|
42
|
+
const dir = cacheDir(version);
|
|
43
|
+
const lib = path.join(dir, libFileName());
|
|
44
|
+
if (fs.existsSync(lib)) return dir;
|
|
45
|
+
|
|
46
|
+
const asset = `viet-asr-native-${platformKey()}.tar.gz`;
|
|
47
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
48
|
+
const tarball = path.join(dir, asset);
|
|
49
|
+
const urls = [
|
|
50
|
+
`https://github.com/${REPO}/releases/download/v${version}/${asset}`,
|
|
51
|
+
`https://github.com/${REPO}/releases/latest/download/${asset}`,
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
let lastErr;
|
|
55
|
+
for (const url of urls) {
|
|
56
|
+
try {
|
|
57
|
+
execFileSync("curl", ["-fSL", "--retry", "3", "-o", tarball, url], {
|
|
58
|
+
stdio: "inherit",
|
|
59
|
+
});
|
|
60
|
+
execFileSync("tar", ["-xzf", tarball, "-C", dir], {
|
|
61
|
+
stdio: "inherit",
|
|
62
|
+
});
|
|
63
|
+
fs.rmSync(tarball, { force: true });
|
|
64
|
+
if (fs.existsSync(lib)) return dir;
|
|
65
|
+
} catch (err) {
|
|
66
|
+
lastErr = err;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
fs.rmSync(tarball, { force: true });
|
|
70
|
+
throw new Error(
|
|
71
|
+
`viet-asr: could not download the native library for ${platformKey()}. ` +
|
|
72
|
+
`Set VIETASR_NATIVE_DIR to a directory containing ${libFileName()}. ` +
|
|
73
|
+
`Cause: ${lastErr && lastErr.message}`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = { platformKey, libFileName, cacheDir, ensureNative };
|
package/lib/native.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const os = require("os");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
|
|
5
|
+
class NativeLibrary {
|
|
6
|
+
constructor() {
|
|
7
|
+
const koffi = require("koffi");
|
|
8
|
+
const nativeDir = NativeLibrary.findNativeDirectory();
|
|
9
|
+
NativeLibrary.preloadSiblings(koffi, nativeDir);
|
|
10
|
+
const libraryPath = NativeLibrary.libraryName(nativeDir);
|
|
11
|
+
this.koffi = koffi;
|
|
12
|
+
this.lib = koffi.load(libraryPath);
|
|
13
|
+
this.declare();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static findNativeDirectory() {
|
|
17
|
+
const override = process.env.VIETASR_NATIVE_DIR;
|
|
18
|
+
if (override && fs.existsSync(override)) {
|
|
19
|
+
return override;
|
|
20
|
+
}
|
|
21
|
+
const candidates = [
|
|
22
|
+
path.resolve(__dirname, "..", "_native"),
|
|
23
|
+
path.resolve(__dirname, "..", "..", "..", "build-core"),
|
|
24
|
+
path.resolve(__dirname, "..", "..", "..", "build-core", "_deps", "onnxruntime-src", "lib"),
|
|
25
|
+
];
|
|
26
|
+
for (const dir of candidates) {
|
|
27
|
+
if (fs.existsSync(path.join(dir, NativeLibrary.libraryFilename()))) {
|
|
28
|
+
return dir;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// Not bundled and not a source checkout: download into the per-user
|
|
32
|
+
// cache (also covers `npm install --ignore-scripts`).
|
|
33
|
+
const { ensureNative } = require("./native-download");
|
|
34
|
+
const { version } = require("../package.json");
|
|
35
|
+
return ensureNative(version);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static libraryFilename() {
|
|
39
|
+
if (process.platform === "win32") return "vietasr.dll";
|
|
40
|
+
if (process.platform === "darwin") return "libvietasr.dylib";
|
|
41
|
+
return "libvietasr.so";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static libraryName(nativeDir) {
|
|
45
|
+
return path.join(nativeDir, NativeLibrary.libraryFilename());
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static preloadSiblings(koffi, dir) {
|
|
49
|
+
if (!fs.existsSync(dir)) return;
|
|
50
|
+
const patterns = process.platform === "win32"
|
|
51
|
+
? [/^onnxruntime.*\.dll$/]
|
|
52
|
+
: process.platform === "darwin"
|
|
53
|
+
? [/^libonnxruntime.*\.dylib$/]
|
|
54
|
+
: [/^libonnxruntime\.so(\..*)?$/];
|
|
55
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
56
|
+
for (const re of patterns) {
|
|
57
|
+
if (re.test(entry)) {
|
|
58
|
+
try {
|
|
59
|
+
koffi.load(path.join(dir, entry), { global: true });
|
|
60
|
+
} catch (_) {
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
declare() {
|
|
68
|
+
const koffi = this.koffi;
|
|
69
|
+
this.VietasrPipeline = koffi.opaque("VietasrPipeline");
|
|
70
|
+
this.VietasrSession = koffi.opaque("VietasrSession");
|
|
71
|
+
const Pipeline = koffi.pointer(this.VietasrPipeline);
|
|
72
|
+
const Session = koffi.pointer(this.VietasrSession);
|
|
73
|
+
|
|
74
|
+
this.fn = {
|
|
75
|
+
vietasr_pipeline_preset: this.lib.func("vietasr_pipeline_preset", Pipeline, ["str"]),
|
|
76
|
+
vietasr_pipeline_new: this.lib.func("vietasr_pipeline_new", Pipeline, []),
|
|
77
|
+
vietasr_pipeline_add_module: this.lib.func("vietasr_pipeline_add_module", "int",
|
|
78
|
+
[Pipeline, "str", "str"]),
|
|
79
|
+
vietasr_pipeline_set_backend: this.lib.func("vietasr_pipeline_set_backend", "int",
|
|
80
|
+
[Pipeline, "int"]),
|
|
81
|
+
vietasr_pipeline_set_model_dir: this.lib.func("vietasr_pipeline_set_model_dir", "int",
|
|
82
|
+
[Pipeline, "str"]),
|
|
83
|
+
vietasr_pipeline_build: this.lib.func("vietasr_pipeline_build", "int", [Pipeline]),
|
|
84
|
+
vietasr_pipeline_free: this.lib.func("vietasr_pipeline_free", "void", [Pipeline]),
|
|
85
|
+
|
|
86
|
+
vietasr_list_modules: this.lib.func("vietasr_list_modules", "str", []),
|
|
87
|
+
vietasr_list_presets: this.lib.func("vietasr_list_presets", "str", []),
|
|
88
|
+
|
|
89
|
+
vietasr_session_new: this.lib.func("vietasr_session_new", Session, [Pipeline, "float"]),
|
|
90
|
+
vietasr_session_free: this.lib.func("vietasr_session_free", "void", [Session]),
|
|
91
|
+
vietasr_session_reset: this.lib.func("vietasr_session_reset", "void", [Session]),
|
|
92
|
+
|
|
93
|
+
vietasr_accept_waveform_s16: this.lib.func("vietasr_accept_waveform_s16", "int",
|
|
94
|
+
[Session, koffi.pointer("int16_t"), "int"]),
|
|
95
|
+
vietasr_accept_waveform_f32: this.lib.func("vietasr_accept_waveform_f32", "int",
|
|
96
|
+
[Session, koffi.pointer("float"), "int"]),
|
|
97
|
+
|
|
98
|
+
vietasr_partial_result: this.lib.func("vietasr_partial_result", "str", [Session]),
|
|
99
|
+
vietasr_result: this.lib.func("vietasr_result", "str", [Session]),
|
|
100
|
+
vietasr_final_result: this.lib.func("vietasr_final_result", "str", [Session]),
|
|
101
|
+
|
|
102
|
+
vietasr_transcribe_file: this.lib.func("vietasr_transcribe_file", "str",
|
|
103
|
+
[Pipeline, "str"]),
|
|
104
|
+
vietasr_transcribe_buffer: this.lib.func("vietasr_transcribe_buffer", "str",
|
|
105
|
+
[Pipeline, koffi.pointer("int16_t"), "int", "float"]),
|
|
106
|
+
|
|
107
|
+
vietasr_default_cache_dir: this.lib.func("vietasr_default_cache_dir", "str", []),
|
|
108
|
+
vietasr_set_log_level: this.lib.func("vietasr_set_log_level", "void", ["int"]),
|
|
109
|
+
vietasr_version: this.lib.func("vietasr_version", "str", []),
|
|
110
|
+
vietasr_last_error: this.lib.func("vietasr_last_error", "str", []),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = new NativeLibrary();
|
package/lib/pipeline.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
const native = require("./native");
|
|
2
|
+
const { Result } = require("./result");
|
|
3
|
+
const { Session } = require("./session");
|
|
4
|
+
|
|
5
|
+
const BACKEND = { auto: 0, onnx: 1, coreml: 2 };
|
|
6
|
+
|
|
7
|
+
class PipelineError extends Error {}
|
|
8
|
+
|
|
9
|
+
class Pipeline {
|
|
10
|
+
constructor(handle) {
|
|
11
|
+
this.handle = handle;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static preset(name) {
|
|
15
|
+
const handle = native.fn.vietasr_pipeline_preset(name);
|
|
16
|
+
if (!handle) {
|
|
17
|
+
throw new PipelineError(Pipeline.lastError() || `unknown preset: ${name}`);
|
|
18
|
+
}
|
|
19
|
+
return new Pipeline(handle);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static new() {
|
|
23
|
+
const handle = native.fn.vietasr_pipeline_new();
|
|
24
|
+
return new Pipeline(handle);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
add(moduleName, config) {
|
|
28
|
+
const json = JSON.stringify(config || {});
|
|
29
|
+
const status = native.fn.vietasr_pipeline_add_module(this.handle, moduleName, json);
|
|
30
|
+
if (status !== 0) {
|
|
31
|
+
throw new PipelineError(Pipeline.lastError() || `add(${moduleName}) failed: ${status}`);
|
|
32
|
+
}
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
setBackend(backend) {
|
|
37
|
+
if (!(backend in BACKEND)) {
|
|
38
|
+
throw new PipelineError(`unknown backend: ${backend}`);
|
|
39
|
+
}
|
|
40
|
+
const status = native.fn.vietasr_pipeline_set_backend(this.handle, BACKEND[backend]);
|
|
41
|
+
if (status !== 0) {
|
|
42
|
+
throw new PipelineError(Pipeline.lastError() || `setBackend(${backend}) failed`);
|
|
43
|
+
}
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
setModelDir(dir) {
|
|
48
|
+
const status = native.fn.vietasr_pipeline_set_model_dir(this.handle, dir);
|
|
49
|
+
if (status !== 0) {
|
|
50
|
+
throw new PipelineError(Pipeline.lastError() || "setModelDir failed");
|
|
51
|
+
}
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
build() {
|
|
56
|
+
const status = native.fn.vietasr_pipeline_build(this.handle);
|
|
57
|
+
if (status !== 0) {
|
|
58
|
+
throw new PipelineError(Pipeline.lastError() || "build failed");
|
|
59
|
+
}
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
transcribe(source, sampleRate) {
|
|
64
|
+
if (typeof source === "string") {
|
|
65
|
+
const raw = native.fn.vietasr_transcribe_file(this.handle, source);
|
|
66
|
+
if (!raw) {
|
|
67
|
+
throw new PipelineError(Pipeline.lastError() || "transcribe_file failed");
|
|
68
|
+
}
|
|
69
|
+
return Result.fromJson(raw);
|
|
70
|
+
}
|
|
71
|
+
if (source instanceof Int16Array) {
|
|
72
|
+
const sr = sampleRate || 16000;
|
|
73
|
+
const raw = native.fn.vietasr_transcribe_buffer(
|
|
74
|
+
this.handle, source, source.length, sr);
|
|
75
|
+
if (!raw) {
|
|
76
|
+
throw new PipelineError(Pipeline.lastError() || "transcribe_buffer failed");
|
|
77
|
+
}
|
|
78
|
+
return Result.fromJson(raw);
|
|
79
|
+
}
|
|
80
|
+
throw new TypeError("transcribe() expects a file path or Int16Array");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
stream(sampleRate) {
|
|
84
|
+
const sr = sampleRate || 16000;
|
|
85
|
+
const handle = native.fn.vietasr_session_new(this.handle, sr);
|
|
86
|
+
if (!handle) {
|
|
87
|
+
throw new PipelineError(Pipeline.lastError() || "session creation failed");
|
|
88
|
+
}
|
|
89
|
+
return new Session(handle);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
static listModules() {
|
|
93
|
+
return JSON.parse(native.fn.vietasr_list_modules() || "[]");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
static listPresets() {
|
|
97
|
+
return JSON.parse(native.fn.vietasr_list_presets() || "[]");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
static lastError() {
|
|
101
|
+
return native.fn.vietasr_last_error() || "";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
static version() {
|
|
105
|
+
return native.fn.vietasr_version() || "";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
static setLogLevel(level) {
|
|
109
|
+
const levels = { trace: 0, debug: 1, info: 2, warn: 3, error: 4, off: 5 };
|
|
110
|
+
const value = typeof level === "string" ? levels[level] : level;
|
|
111
|
+
native.fn.vietasr_set_log_level(value);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
close() {
|
|
115
|
+
if (this.handle) {
|
|
116
|
+
native.fn.vietasr_pipeline_free(this.handle);
|
|
117
|
+
this.handle = null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = { Pipeline, PipelineError, Session, Result };
|
package/lib/result.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
class Result {
|
|
2
|
+
constructor(payload) {
|
|
3
|
+
this.payload = payload;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
static fromJson(raw) {
|
|
7
|
+
if (!raw) return new Result({});
|
|
8
|
+
try {
|
|
9
|
+
return new Result(JSON.parse(raw));
|
|
10
|
+
} catch (_) {
|
|
11
|
+
return new Result({});
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
get text() {
|
|
16
|
+
return this.payload.text || "";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
get partial() {
|
|
20
|
+
return this.payload.partial || "";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get isFinal() {
|
|
24
|
+
return Boolean(this.payload.is_final);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get segments() {
|
|
28
|
+
return this.payload.segments || [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get speakers() {
|
|
32
|
+
return this.payload.speakers || [];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
field(key) {
|
|
36
|
+
return this.payload[key];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
toJson() {
|
|
40
|
+
return JSON.stringify(this.payload);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = { Result };
|
package/lib/session.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const native = require("./native");
|
|
2
|
+
const { Result } = require("./result");
|
|
3
|
+
|
|
4
|
+
class Session {
|
|
5
|
+
constructor(handle) {
|
|
6
|
+
this.handle = handle;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
reset() {
|
|
10
|
+
native.fn.vietasr_session_reset(this.handle);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
accept(pcm) {
|
|
14
|
+
if (!this.handle) {
|
|
15
|
+
throw new Error("Session is closed");
|
|
16
|
+
}
|
|
17
|
+
if (pcm instanceof Int16Array) {
|
|
18
|
+
return native.fn.vietasr_accept_waveform_s16(
|
|
19
|
+
this.handle, pcm, pcm.length) === 1;
|
|
20
|
+
}
|
|
21
|
+
if (pcm instanceof Float32Array) {
|
|
22
|
+
return native.fn.vietasr_accept_waveform_f32(
|
|
23
|
+
this.handle, pcm, pcm.length) === 1;
|
|
24
|
+
}
|
|
25
|
+
if (Buffer.isBuffer(pcm)) {
|
|
26
|
+
const view = new Int16Array(pcm.buffer, pcm.byteOffset,
|
|
27
|
+
pcm.byteLength / 2);
|
|
28
|
+
return native.fn.vietasr_accept_waveform_s16(
|
|
29
|
+
this.handle, view, view.length) === 1;
|
|
30
|
+
}
|
|
31
|
+
throw new TypeError(
|
|
32
|
+
"accept() expects Int16Array, Float32Array, or Buffer (16-bit PCM)");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
partial() {
|
|
36
|
+
return Result.fromJson(native.fn.vietasr_partial_result(this.handle));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
result() {
|
|
40
|
+
return Result.fromJson(native.fn.vietasr_result(this.handle));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
final() {
|
|
44
|
+
return Result.fromJson(native.fn.vietasr_final_result(this.handle));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
close() {
|
|
48
|
+
if (this.handle) {
|
|
49
|
+
native.fn.vietasr_session_free(this.handle);
|
|
50
|
+
this.handle = null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = { Session };
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "viet-asr",
|
|
3
|
+
"version": "0.1.0-dev.3",
|
|
4
|
+
"description": "Universal Vietnamese Speech AI SDK",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./index.js"
|
|
8
|
+
},
|
|
9
|
+
"bin": {
|
|
10
|
+
"vietasr": "./bin/vietasr.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"index.js",
|
|
14
|
+
"lib/",
|
|
15
|
+
"bin/",
|
|
16
|
+
"scripts/",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"keywords": [
|
|
20
|
+
"asr",
|
|
21
|
+
"speech-recognition",
|
|
22
|
+
"vietnamese",
|
|
23
|
+
"vietasr",
|
|
24
|
+
"offline",
|
|
25
|
+
"onnx"
|
|
26
|
+
],
|
|
27
|
+
"license": "Apache-2.0",
|
|
28
|
+
"homepage": "https://github.com/dangvansam/viet-asr",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/dangvansam/viet-asr.git",
|
|
32
|
+
"directory": "bindings/nodejs"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=16"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"koffi": "^2.10.0"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"postinstall": "node scripts/install.js",
|
|
42
|
+
"test": "node test/smoke.js",
|
|
43
|
+
"example": "node examples/quickstart.js"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// postinstall hook — pre-fetch the native library so the first require() is
|
|
4
|
+
// instant. Failure here is non-fatal: lib/native.js retries lazily at runtime
|
|
5
|
+
// (covers offline installs and `npm install --ignore-scripts`).
|
|
6
|
+
|
|
7
|
+
const { ensureNative } = require("../lib/native-download");
|
|
8
|
+
const { version } = require("../package.json");
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
ensureNative(version);
|
|
12
|
+
console.log(`viet-asr: native library ready (v${version})`);
|
|
13
|
+
} catch (err) {
|
|
14
|
+
console.warn(`viet-asr: native prefetch skipped — ${err.message}`);
|
|
15
|
+
}
|