openvino-genai-node
Version:
OpenVINO™ GenAI pipelines for using from Node.js environment
165 lines • 6.96 kB
JavaScript
// Copyright (C) 2023-2026 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
import util from "node:util";
import { WhisperPipeline as WhisperPipelineWrapper } from "../addon.js";
import { WhisperDecodedResults, } from "../decodedResults.js";
import { StreamingStatus } from "../utils.js";
/**
* Pipeline for automatic speech recognition using Whisper models.
*
* Expects raw audio normalized to approximately [-1, 1] at 16 kHz sample rate.
* Use a WAV file or decode audio to Float32Array before calling generate().
*/
export class WhisperPipeline {
/**
* Construct a Whisper pipeline from a folder containing model IRs and tokenizer.
* @param modelPath - Path to the folder with model IRs and tokenizer (e.g. openvino_encoder_model.xml, preprocessor_config.json).
* @param device - Inference device (e.g. "CPU", "GPU").
* @param properties - Device and pipeline properties (e.g. word_timestamps: true, CACHE_DIR: "cache").
*/
constructor(modelPath, device, properties = {}) {
this.pipeline = null;
this.modelPath = modelPath;
this.device = device;
this.properties = properties;
}
/**
* Load the pipeline. Must be called once before generate().
*/
async init() {
const pipeline = new WhisperPipelineWrapper();
const initPromise = util.promisify(pipeline.init.bind(pipeline));
await initPromise(this.modelPath, this.device, this.properties);
this.pipeline = pipeline;
}
/**
* Stream speech recognition results as an async iterator.
* The iterator yields decoded text chunks during generation.
* When generation finishes, the full decoded text is returned as the final
* iterator value (`done: true`). This value is not available through
* `for await...of`; call `next()` directly to read it.
*
* For custom streaming control, use {@link generate} with a streamer callback instead.
*
* @param rawSpeech - Audio samples as Float32Array or number[], normalized to ~[-1, 1], 16 kHz.
* @param options - Optional generation config (e.g. language, task, return_timestamps).
* @returns Async iterator that yields decoded text chunks as strings.
*/
stream(rawSpeech, options) {
var _a;
if (!this.pipeline)
throw new Error("WhisperPipeline is not initialized");
const generationConfig = (_a = options === null || options === void 0 ? void 0 : options.generationConfig) !== null && _a !== void 0 ? _a : {};
let streamingStatus = StreamingStatus.RUNNING;
let handledError;
const queue = [];
let resolvePromise = null;
let rejectPromise = null;
const callback = (error, result) => {
var _a, _b;
if (error) {
if (rejectPromise) {
rejectPromise(error);
resolvePromise = null;
rejectPromise = null;
}
else {
handledError = error;
}
}
else {
const fullText = (_b = (_a = result.texts) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : "";
if (resolvePromise) {
resolvePromise({ done: true, value: fullText });
resolvePromise = null;
rejectPromise = null;
}
else {
queue.push({ done: true, chunk: fullText });
}
}
};
const streamer = (chunk) => {
if (resolvePromise) {
resolvePromise({ done: false, value: chunk });
resolvePromise = null;
rejectPromise = null;
}
else {
queue.push({ done: false, chunk });
}
return streamingStatus;
};
this.pipeline.generate(rawSpeech, generationConfig, streamer, callback);
return {
async next() {
if (handledError) {
const error = handledError;
handledError = null;
return Promise.reject(error);
}
const data = queue.shift();
if (data) {
return { value: data.chunk, done: data.done };
}
return new Promise((resolve, reject) => {
resolvePromise = resolve;
rejectPromise = reject;
});
},
async return() {
streamingStatus = StreamingStatus.CANCEL;
return { done: true, value: "" };
},
[Symbol.asyncIterator]() {
return this;
},
};
}
/**
* Run speech recognition with optional streaming.
*
* For simple streaming use cases, consider using {@link stream}, which provides
* a convenient async iterator interface.
*
* @param rawSpeech - Audio samples as Float32Array or number[], normalized to ~[-1, 1], 16 kHz.
* @param options - Optional parameters.
* @param options.generationConfig - Generation config (e.g., language, task, return_timestamps).
* @param options.streamer - Optional callback invoked for each decoded chunk.
* - Return a `StreamingStatus` flag to indicate whether generation should be stopped or cancelled
* @returns Decoded texts, scores, optional chunks with timestamps, and perf metrics.
*/
async generate(rawSpeech, options = {}) {
if (!this.pipeline)
throw new Error("WhisperPipeline is not initialized");
const { generationConfig, streamer } = options;
const generatePromise = util.promisify(this.pipeline.generate.bind(this.pipeline));
const res = await generatePromise(rawSpeech, generationConfig !== null && generationConfig !== void 0 ? generationConfig : {}, streamer);
return new WhisperDecodedResults(res.texts, res.scores, res.perfMetrics, res.chunks, res.words);
}
/**
* Get the pipeline tokenizer.
*/
getTokenizer() {
if (!this.pipeline)
throw new Error("WhisperPipeline is not initialized");
return this.pipeline.getTokenizer();
}
/**
* Get current generation config (language, task, return_timestamps, etc.).
*/
getGenerationConfig() {
if (!this.pipeline)
throw new Error("WhisperPipeline is not initialized");
return this.pipeline.getGenerationConfig();
}
/**
* Update generation config (e.g. language, task, return_timestamps).
*/
setGenerationConfig(config) {
if (!this.pipeline)
throw new Error("WhisperPipeline is not initialized");
this.pipeline.setGenerationConfig(config);
}
}
//# sourceMappingURL=whisperPipeline.js.map