fix: copy Silero VAD model out of asar so native code can read it
The bundled silero_vad.onnx lives inside Electron's app.asar which native C++ (sherpa-onnx) cannot open via OS file I/O. On first voice activation, copy the model to ~/.paseo/models/local-speech/silero-vad/ using Node.js fs (asar-aware) and point the native code there.
This commit is contained in:
@@ -22,7 +22,7 @@ import { SherpaParakeetRealtimeTranscriptionSession } from "./sherpa/sherpa-para
|
|||||||
import { SherpaRealtimeTranscriptionSession } from "./sherpa/sherpa-realtime-session.js";
|
import { SherpaRealtimeTranscriptionSession } from "./sherpa/sherpa-realtime-session.js";
|
||||||
import { SherpaOnnxSTT } from "./sherpa/sherpa-stt.js";
|
import { SherpaOnnxSTT } from "./sherpa/sherpa-stt.js";
|
||||||
import { SherpaOnnxTTS } from "./sherpa/sherpa-tts.js";
|
import { SherpaOnnxTTS } from "./sherpa/sherpa-tts.js";
|
||||||
import { SherpaSileroTurnDetectionProvider } from "./sherpa/silero-vad-provider.js";
|
import { ensureSileroVadModel, SherpaSileroTurnDetectionProvider } from "./sherpa/silero-vad-provider.js";
|
||||||
|
|
||||||
type LocalSttEngine =
|
type LocalSttEngine =
|
||||||
| { kind: "offline"; engine: SherpaOfflineRecognizerEngine }
|
| { kind: "offline"; engine: SherpaOfflineRecognizerEngine }
|
||||||
@@ -236,7 +236,15 @@ export async function initializeLocalSpeechServices(params: {
|
|||||||
providers.voiceTurnDetection.enabled !== false &&
|
providers.voiceTurnDetection.enabled !== false &&
|
||||||
providers.voiceTurnDetection.provider === "local"
|
providers.voiceTurnDetection.provider === "local"
|
||||||
) {
|
) {
|
||||||
turnDetectionService = new SherpaSileroTurnDetectionProvider({}, logger);
|
let vadModelPath: string | undefined;
|
||||||
|
if (localConfig) {
|
||||||
|
try {
|
||||||
|
vadModelPath = await ensureSileroVadModel(localConfig.modelsDir, logger);
|
||||||
|
} catch (err) {
|
||||||
|
logger.warn({ err }, "Failed to provision Silero VAD model, falling back to bundled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
turnDetectionService = new SherpaSileroTurnDetectionProvider({ modelPath: vadModelPath }, logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (providers.voiceStt.enabled !== false && providers.voiceStt.provider === "local") {
|
if (providers.voiceStt.enabled !== false && providers.voiceStt.provider === "local") {
|
||||||
|
|||||||
@@ -1,10 +1,43 @@
|
|||||||
|
import { copyFile, mkdir, stat } from "node:fs/promises";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
import type { Logger } from "pino";
|
import type { Logger } from "pino";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
TurnDetectionProvider,
|
TurnDetectionProvider,
|
||||||
TurnDetectionSession,
|
TurnDetectionSession,
|
||||||
} from "../../../turn-detection-provider.js";
|
} from "../../../turn-detection-provider.js";
|
||||||
import { SherpaSileroVadSession, type SherpaSileroVadSessionConfig } from "./silero-vad-session.js";
|
import {
|
||||||
|
resolveBundledSileroVadModelPath,
|
||||||
|
SherpaSileroVadSession,
|
||||||
|
type SherpaSileroVadSessionConfig,
|
||||||
|
} from "./silero-vad-session.js";
|
||||||
|
|
||||||
|
const SILERO_VAD_DIR = "silero-vad";
|
||||||
|
const SILERO_VAD_FILE = "silero_vad.onnx";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure the Silero VAD ONNX model exists in modelsDir where native code can
|
||||||
|
* read it. The bundled asset lives inside Electron's app.asar which native C++
|
||||||
|
* cannot open, so we copy it out on first run using Node.js fs (asar-aware).
|
||||||
|
*/
|
||||||
|
export async function ensureSileroVadModel(modelsDir: string, logger: Logger): Promise<string> {
|
||||||
|
const destDir = path.join(modelsDir, SILERO_VAD_DIR);
|
||||||
|
const destPath = path.join(destDir, SILERO_VAD_FILE);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const s = await stat(destPath);
|
||||||
|
if (s.isFile() && s.size > 0) return destPath;
|
||||||
|
} catch {
|
||||||
|
// not present yet
|
||||||
|
}
|
||||||
|
|
||||||
|
const bundledPath = resolveBundledSileroVadModelPath();
|
||||||
|
await mkdir(destDir, { recursive: true });
|
||||||
|
await copyFile(bundledPath, destPath);
|
||||||
|
logger.info({ destPath }, "Copied Silero VAD model to models directory");
|
||||||
|
return destPath;
|
||||||
|
}
|
||||||
|
|
||||||
export class SherpaSileroTurnDetectionProvider implements TurnDetectionProvider {
|
export class SherpaSileroTurnDetectionProvider implements TurnDetectionProvider {
|
||||||
public readonly id = "local" as const;
|
public readonly id = "local" as const;
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ type SherpaVadModule = {
|
|||||||
CircularBuffer: new (capacity: number) => SherpaCircularBufferHandle;
|
CircularBuffer: new (capacity: number) => SherpaCircularBufferHandle;
|
||||||
};
|
};
|
||||||
|
|
||||||
function resolveBundledSileroVadModelPath(): string {
|
export function resolveBundledSileroVadModelPath(): string {
|
||||||
return fileURLToPath(new URL("./assets/silero_vad.onnx", import.meta.url));
|
return fileURLToPath(new URL("./assets/silero_vad.onnx", import.meta.url));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user