Add realtime loopback STT prototype
This commit is contained in:
107
src/index.ts
Normal file
107
src/index.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import process from "node:process";
|
||||
|
||||
import { loadConfig } from "./config.js";
|
||||
import { Logger } from "./logger.js";
|
||||
import { printAudioDevices, spawnLoopbackCapture } from "./audio/capture.js";
|
||||
import { RealtimeSegmenter } from "./audio/realtime-segmenter.js";
|
||||
import { FasterWhisperSttService } from "./services/faster-whisper-stt.js";
|
||||
|
||||
const mode = process.argv[2] ?? "loopback";
|
||||
|
||||
async function runLoopback(): Promise<void> {
|
||||
const config = loadConfig();
|
||||
const logger = new Logger(config.LOG_LEVEL);
|
||||
const stt = new FasterWhisperSttService(config, logger);
|
||||
|
||||
await stt.warmup();
|
||||
|
||||
const transcriptionQueue: Buffer[] = [];
|
||||
let transcribing = false;
|
||||
|
||||
const runNext = async (): Promise<void> => {
|
||||
if (transcribing) {
|
||||
return;
|
||||
}
|
||||
const next = transcriptionQueue.shift();
|
||||
if (!next) {
|
||||
return;
|
||||
}
|
||||
|
||||
transcribing = true;
|
||||
try {
|
||||
const text = await stt.transcribePcm16(next);
|
||||
if (!text) {
|
||||
logger.info("빈 전사 결과");
|
||||
} else {
|
||||
logger.info("Transcript", text);
|
||||
if (config.DEBUG_TRANSCRIPTS) {
|
||||
console.log(`\n[text] ${text}\n`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn("STT failed", error);
|
||||
} finally {
|
||||
transcribing = false;
|
||||
void runNext();
|
||||
}
|
||||
};
|
||||
|
||||
const segmenter = new RealtimeSegmenter({
|
||||
onSegment: (pcm16) => {
|
||||
transcriptionQueue.push(pcm16);
|
||||
void runNext();
|
||||
},
|
||||
});
|
||||
|
||||
const capture = spawnLoopbackCapture(config, logger);
|
||||
capture.stdout.on("data", (chunk: Buffer) => {
|
||||
segmenter.pushChunk(chunk);
|
||||
});
|
||||
capture.stderr.on("data", (chunk: Buffer) => {
|
||||
const text = chunk.toString().trim();
|
||||
if (text) {
|
||||
logger.debug("[capture]", text);
|
||||
}
|
||||
});
|
||||
capture.on("exit", (code, signal) => {
|
||||
logger.warn("capture exited", { code, signal });
|
||||
});
|
||||
|
||||
console.log("실시간 출력장치 STT를 시작합니다. Ctrl+C 로 종료합니다.");
|
||||
console.log(`source: ${config.AUDIO_SOURCE ?? "unset"}`);
|
||||
console.log(`model: ${config.WHISPER_MODEL}`);
|
||||
console.log(`language: ${config.WHISPER_LANGUAGE}`);
|
||||
|
||||
const shutdown = async (): Promise<void> => {
|
||||
if (!capture.killed) {
|
||||
capture.kill("SIGTERM");
|
||||
}
|
||||
await stt.destroy();
|
||||
process.exit(0);
|
||||
};
|
||||
|
||||
process.on("SIGINT", () => {
|
||||
void shutdown();
|
||||
});
|
||||
process.on("SIGTERM", () => {
|
||||
void shutdown();
|
||||
});
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
switch (mode) {
|
||||
case "devices":
|
||||
await printAudioDevices();
|
||||
return;
|
||||
case "loopback":
|
||||
await runLoopback();
|
||||
return;
|
||||
default:
|
||||
throw new Error(`알 수 없는 실행 모드입니다: ${mode}. 사용 가능: loopback, devices`);
|
||||
}
|
||||
}
|
||||
|
||||
void main().catch((error) => {
|
||||
console.error(error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user