@desert-ant-labs/voz

中文文档
4.3.0发布于 2026/11/06SEE LICENSE IN LICENSE.mdTypeScriptES Module近 30 天下载 8644

On-device speech recognition for JavaScript: transcripts with word-level timestamps, 25 languages. Runs in the browser on WebAssembly + ONNX Runtime Web (WebGPU, with the decode step on WebNN where the browser has it), and server-side in Node from one imp

原始使用文档
文档目录7 节
  1. Word timestamps
  2. What you can pass to transcribe
  3. Memory
  4. Where it runs
  5. Loading the model
  6. Requirements
  7. License

@desert-ant-labs/voz

On-device speech recognition for JavaScript that runs in the browser and in Node. Give Voz audio and it returns a transcript with a start and an end on every word, across 25 languages. Audio stays local: nothing is uploaded, and there is no API key.

One entry, @desert-ant-labs/voz, and one Voz API. Unlike this project's other models there is no /native subpath: Voz has no native Node core, so the browser and the server run the same WebAssembly pipeline - the same windowing, lane-batched decode and splice as the Swift SDK - over ONNX Runtime.

# Browser
npm i @desert-ant-labs/voz onnxruntime-web

# Node
npm i @desert-ant-labs/voz onnxruntime-node

In the browser you install the runtime and nothing else. It is imported on demand, the way @litertjs/core is for this project's other models, and bundlers split it into its own chunk, so a page that never transcribes never downloads it.

import { Voz } from "@desert-ant-labs/voz";

const voz = await Voz.load();
const result = await voz.transcribe(file);   // a File, Blob, ArrayBuffer, or samples

result.text;            // "the transcript, as one string"
result.words[0];        // { text: "the", start: 0.08, end: 0.24 }
result.realtimeFactor;  // seconds of audio per second of wall clock

The file is read in pieces as the model works through it, so a five-minute recording and a five-hour one cost the same memory. See Memory for what that means in numbers.

Under Node you pass the runtime, because there it is onnxruntime-node, a native addon. A package that imports one of those cannot be bundled for a server, so this one does not.

import { Voz } from "@desert-ant-labs/voz";
import * as ort from "onnxruntime-node";

const voz = await Voz.load({ ort });

Passing ort works in the browser too, for an app that already bundles a runtime or wants a build other than onnxruntime-web/webgpu.

The bundle is downloaded from Hugging Face on first use and cached (the Cache API in a browser, disk in Node), so a reload does not fetch it again. Nothing model-sized ships in the npm tarball.

Word timestamps

Every word carries start and end in seconds from the beginning of the audio, which is what subtitles, transcript highlighting and clip-finding need:

const { words } = await voz.transcribe(file);

for (const { text, start, end } of words) {
  console.log(`${start.toFixed(2)} -> ${end.toFixed(2)}  ${text}`);
}

Times come from the frame that emitted each word rather than from a second alignment pass, so they cost nothing extra. Their resolution is one encoder frame, 80 ms.

What you can pass to transcribe

InputWhat happens
File, BlobStreamed: read in pieces, any container WebCodecs can decode
a path (Node 20+)Streamed, the same way
ArrayBuffer, Uint8ArrayDecoded whole, since the bytes are already in memory
Float32ArrayTaken as mono 16 kHz samples
{ samples, sampleRate }Resampled to 16 kHz

A browser can hand over a <input type="file"> file directly:

input.addEventListener("change", async () => {
  const result = await voz.transcribe(input.files[0], {
    onProgress: (fraction) => console.log(`${Math.round(fraction * 100)}%`),
  });
  console.log(result.text);
});

Under Node, a path streams the same way a File does, which needs Node 20 or newer (fs.openAsBlob). WebCodecs is what decodes compressed audio, so on a Node build without it, convert to WAV first - ffmpeg -i in.mp4 -ac 1 -ar 16000 out.wav - which streams everywhere.

If a decoder ever returns less audio than the container says it holds, that is reported rather than transcribed: decodeAudioData can return a short buffer with no error, and a transcript that quietly stops a minute in is worse than a failure. In a video file, where the audio track is allowed to be shorter than the picture, it is a warning instead.

Memory

Constant in the length of the recording, which is the point of the streaming path above. Measured in Chromium on an M5, peak resident for the whole browser:

audiopeak
1 minute2.4 GB
29 minutes2.7 GB
29 minutes, before streaming7.7 GB

About 1.2 GB of that floor is the model: 349 MB of weights, and roughly 900 MB that ONNX Runtime keeps for the compiled session (measured, and not something this package can configure away). The rest is the browser itself. What matters is the shape: the audio no longer contributes, so length is free.

Where it runs

In the browser the encoder runs on WebGPU and, where the browser exposes WebNN (Chromium today), the decode step runs on the Neural Engine. Both are detected; Voz.load({ webnn: false }) forces WebGPU alone.

Speed, ten minutes of audio: about 125x real time in Chromium on an M5, 38x on an M1, 255x on an M3 Ultra, and about 35x in Safari. Word error rate is level with the Core ML build the Apple SDK uses (2.83% against 2.66% over 300 LibriSpeech utterances, a difference a paired bootstrap cannot distinguish).

Under Node, onnxruntime-node runs on the CPU. That is the right choice for transcribing a file on a server, and it is slower per second of audio than the browser path on the same machine, because a browser reaches the GPU and onnxruntime-node's default execution provider does not.

The page needs no SharedArrayBuffer and no cross-origin isolation: the runtime is configured for a single wasm thread, which measured identical on Chromium and avoids a WebKit JIT pathology that costs 15% in Safari.

Loading the model

Voz.load() takes:

OptionDefault
ortonnxruntime-web in the browseran ONNX Runtime module of your own; required under Node
ep"auto""webgpu" or "wasm" to override where the graphs run
modelBaseUrlHugging Faceserve the bundle yourself; must end in /
revisionthe SDK's pina different revision of the weights
webnndetectedrun the decode step on WebNN
wasmDirthe runtime's ownwhere onnxruntime-web's .wasm files are served from
onProgressnonedownload progress, as a fraction
cachetruecache the downloaded bundle

To serve the weights yourself, copy the web/ directory of the weights repo next to your app and point modelBaseUrl at it. meta.json names the rest, so there is no file list to keep in sync here.

One instance holds three compiled graphs and about 1.2 GB of memory (349 MB of it weights), so load it once and reuse it. A transcription holds the instance for its whole run, and concurrent calls queue rather than interleave.

Requirements

  • A browser with WebGPU. Tested on Chromium 135+ and Safari 26+. A browser whose adapter cannot compile the encoder's f16 shaders - which is what a machine with no GPU exposes - runs on the CPU instead, correctly and much more slowly.
  • Node 20+ for streaming from a path, with onnxruntime-node installed.

License

Desert Ant Labs Source-Available License. Free for most apps, and a commercial license is required at scale. Full terms are at the link. Licensing: [email protected].

Third-party notices for the SDK: THIRD_PARTY_NOTICES.md.

Third-party notices for the weights: THIRD_PARTY_NOTICES.md.

版本与发布记录

@desert-ant-labs/voz 共发布 10 个版本,首次发布于 2026/09/24;过去 12 个月发布了 10 个版本。

版本发布日期距今
4.3.0latest2026/11/066 天前
4.2.32026/10/3112 天前
4.2.22026/10/3112 天前
4.2.12026/10/2815 天前
4.2.02026/10/2716 天前
4.1.02026/10/2023 天前

关于 @desert-ant-labs/voz 的常见问题

@desert-ant-labs/voz 是什么?

On-device speech recognition for JavaScript: transcripts with word-level timestamps, 25 languages. Runs in the browser on WebAssembly + ONNX Runtime Web (WebGPU, with the decode step on WebNN where the browser has it), and server-side in Node from one imp

如何安装 @desert-ant-labs/voz?

在项目目录运行 npm install @desert-ant-labs/voz,或使用 pnpm add / yarn add / bun add。

@desert-ant-labs/voz 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@desert-ant-labs/voz 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@desert-ant-labs/voz 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@desert-ant-labs/voz 可以商用吗?

@desert-ant-labs/voz 使用 SEE LICENSE IN LICENSE.md 许可。许可条款写在包内的独立文件中,请阅读该文件确认可否商用。以上为概括,不构成法律意见,请以许可原文为准。

@desert-ant-labs/voz 的下载量有多少?

根据 npm 官方下载统计,@desert-ant-labs/voz 在 2026/10/12 – 2026/11/10 这 30 天内被下载 8,644 次,日均约 288 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@desert-ant-labs/voz 还在维护吗?

最新版本 4.3.0 发布于 2026/11/06(6 天前);至今共发布 10 个版本,其中过去 12 个月发布了 10 个;首次发布于 2026/09/24。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。