omnius 1.0.420 → 1.0.421
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/dist/index.js +94 -12
- package/dist/scripts/live-whisper.py +96 -10
- package/npm-shrinkwrap.json +5 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -603509,6 +603509,8 @@ var init_listen = __esm({
|
|
|
603509
603509
|
model: this.model,
|
|
603510
603510
|
lastStatus: `loading whisper ${this.model} model...`
|
|
603511
603511
|
});
|
|
603512
|
+
const consensusEnv = String(process.env["OMNIUS_ASR_CONSENSUS"] ?? "").trim().toLowerCase();
|
|
603513
|
+
const consensusModel = consensusEnv === "0" || consensusEnv === "off" || consensusEnv === "false" ? "off" : consensusEnv || (this.model === "tiny" ? "base" : "tiny");
|
|
603512
603514
|
this.process = spawn29(
|
|
603513
603515
|
pyPath,
|
|
603514
603516
|
[
|
|
@@ -603518,7 +603520,9 @@ var init_listen = __esm({
|
|
|
603518
603520
|
"--chunk-seconds",
|
|
603519
603521
|
"3",
|
|
603520
603522
|
"--window-seconds",
|
|
603521
|
-
"10"
|
|
603523
|
+
"10",
|
|
603524
|
+
"--consensus-model",
|
|
603525
|
+
consensusModel
|
|
603522
603526
|
],
|
|
603523
603527
|
{
|
|
603524
603528
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -603555,6 +603559,11 @@ var init_listen = __esm({
|
|
|
603555
603559
|
isFinal: evt.isFinal ?? false
|
|
603556
603560
|
});
|
|
603557
603561
|
break;
|
|
603562
|
+
case "consensus_rejected":
|
|
603563
|
+
updateListenLiveState({
|
|
603564
|
+
lastStatus: `consensus rejected (${evt.reason ?? "mismatch"}): ${String(evt.primary ?? "").slice(0, 80)}`
|
|
603565
|
+
});
|
|
603566
|
+
break;
|
|
603558
603567
|
case "error":
|
|
603559
603568
|
this.emit("error", new Error(evt.message));
|
|
603560
603569
|
break;
|
|
@@ -605469,7 +605478,7 @@ function formatLiveSensorContextFromSnapshot(snapshot, opts = {}) {
|
|
|
605469
605478
|
}
|
|
605470
605479
|
if (snapshot.config.selectedAudioInput) lines.push(`Selected audio input: ${snapshot.config.selectedAudioInput}`);
|
|
605471
605480
|
if (snapshot.config.selectedAudioOutput) lines.push(`Selected audio output: ${snapshot.config.selectedAudioOutput}`);
|
|
605472
|
-
const cameraEvents = (snapshot.events ?? []).filter((event) => event.kind.startsWith("camera_") || event.kind === "clip_recognition" || event.kind === "visual_trigger").sort((a2, b) => b.observedAt - a2.observedAt).slice(0, 18);
|
|
605481
|
+
const cameraEvents = (snapshot.events ?? []).filter((event) => event.kind.startsWith("camera_") || event.kind === "clip_recognition" || event.kind === "visual_trigger").sort((a2, b) => b.observedAt - a2.observedAt).slice(0, toolCapable ? 18 : 6);
|
|
605473
605482
|
if (activeVideo) {
|
|
605474
605483
|
lines.push("");
|
|
605475
605484
|
lines.push("## LIVE CAMERA EVENTS");
|
|
@@ -605519,10 +605528,10 @@ function formatLiveSensorContextFromSnapshot(snapshot, opts = {}) {
|
|
|
605519
605528
|
if (snapshot.video.framePath) lines.push(`frame: ${snapshot.video.framePath}`);
|
|
605520
605529
|
const classifications = cameraClassificationsSummary(snapshot.video);
|
|
605521
605530
|
if (classifications) lines.push(`Live classifications: ${classifications}`);
|
|
605522
|
-
if (snapshot.video.asciiContext) lines.push(snapshot.video.asciiContext);
|
|
605531
|
+
if (toolCapable && snapshot.video.asciiContext) lines.push(snapshot.video.asciiContext);
|
|
605523
605532
|
if (snapshot.video.inference) {
|
|
605524
605533
|
lines.push("Video inference:");
|
|
605525
|
-
lines.push(cleanPreview(snapshot.video.inference, 1800));
|
|
605534
|
+
lines.push(cleanPreview(snapshot.video.inference, toolCapable ? 1800 : 600));
|
|
605526
605535
|
}
|
|
605527
605536
|
}
|
|
605528
605537
|
if (cameraSnapshots.length > 1) {
|
|
@@ -614228,6 +614237,7 @@ __export(render_exports, {
|
|
|
614228
614237
|
SLASH_COMMANDS: () => SLASH_COMMANDS2,
|
|
614229
614238
|
applyCollapseToBox: () => applyCollapseToBox,
|
|
614230
614239
|
breakTelegramCoalesce: () => breakTelegramCoalesce,
|
|
614240
|
+
breakVoiceChatCoalesce: () => breakVoiceChatCoalesce,
|
|
614231
614241
|
c: () => c3,
|
|
614232
614242
|
charWidth: () => charWidth2,
|
|
614233
614243
|
fileLink: () => fileLink,
|
|
@@ -614264,6 +614274,7 @@ __export(render_exports, {
|
|
|
614264
614274
|
renderUserInterrupt: () => renderUserInterrupt,
|
|
614265
614275
|
renderUserMessage: () => renderUserMessage,
|
|
614266
614276
|
renderVerbose: () => renderVerbose,
|
|
614277
|
+
renderVoiceChatCoalescedRow: () => renderVoiceChatCoalescedRow,
|
|
614267
614278
|
renderVoiceText: () => renderVoiceText,
|
|
614268
614279
|
renderWarning: () => renderWarning,
|
|
614269
614280
|
setColorsEnabled: () => setColorsEnabled,
|
|
@@ -615020,6 +615031,63 @@ function renderTelegramCoalescedRow(row, opts) {
|
|
|
615020
615031
|
);
|
|
615021
615032
|
host.appendDynamicBlock(id2);
|
|
615022
615033
|
}
|
|
615034
|
+
function breakVoiceChatCoalesce() {
|
|
615035
|
+
_voiceChatCoalesce = null;
|
|
615036
|
+
}
|
|
615037
|
+
function renderVoiceChatCoalescedRow(row, opts) {
|
|
615038
|
+
const colorCode = opts?.colorCode ?? 45;
|
|
615039
|
+
const title = opts?.title ?? "☏ VoiceChat";
|
|
615040
|
+
const redir = _contentWriteHook?.redirect?.();
|
|
615041
|
+
const host = _contentWriteHook?.dynamicBlockHost?.() ?? null;
|
|
615042
|
+
if (redir || !host || typeof host.refreshDynamicBlocks !== "function") {
|
|
615043
|
+
_voiceChatCoalesce = null;
|
|
615044
|
+
const text2 = `${row}
|
|
615045
|
+
`;
|
|
615046
|
+
if (redir) redir(text2);
|
|
615047
|
+
else process.stdout.write(text2);
|
|
615048
|
+
return;
|
|
615049
|
+
}
|
|
615050
|
+
if (_voiceChatCoalesce) {
|
|
615051
|
+
_voiceChatCoalesce.rows.push(row);
|
|
615052
|
+
host.refreshDynamicBlocks();
|
|
615053
|
+
return;
|
|
615054
|
+
}
|
|
615055
|
+
const id2 = `voicechat-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
615056
|
+
const state = { id: id2, rows: [row], expanded: false, colorCode };
|
|
615057
|
+
_voiceChatCoalesce = state;
|
|
615058
|
+
host.registerDynamicBlock(id2, (width) => {
|
|
615059
|
+
const visible = state.expanded ? state.rows : state.rows.slice(-VOICECHAT_VISIBLE_ROWS);
|
|
615060
|
+
const hidden = state.rows.length - visible.length;
|
|
615061
|
+
const body = [];
|
|
615062
|
+
if (!state.expanded && hidden > 0) {
|
|
615063
|
+
body.push({ text: `▸ ${hidden} ${VOICECHAT_EXPAND_LABEL}`, mode: "truncate", kind: "dim" });
|
|
615064
|
+
} else if (state.expanded && state.rows.length > VOICECHAT_VISIBLE_ROWS) {
|
|
615065
|
+
body.push({ text: `▾ ${VOICECHAT_COLLAPSE_LABEL}`, mode: "truncate", kind: "dim" });
|
|
615066
|
+
}
|
|
615067
|
+
for (const text2 of visible) {
|
|
615068
|
+
body.push({ text: text2, mode: "wrap", kind: "plain" });
|
|
615069
|
+
}
|
|
615070
|
+
return buildToolBoxLines(
|
|
615071
|
+
{
|
|
615072
|
+
title,
|
|
615073
|
+
metrics: state.rows.length > 2 ? `${Math.ceil(state.rows.length / 2)} turns` : "",
|
|
615074
|
+
body,
|
|
615075
|
+
colorCode: state.colorCode
|
|
615076
|
+
},
|
|
615077
|
+
width
|
|
615078
|
+
);
|
|
615079
|
+
});
|
|
615080
|
+
host.registerDynamicBlockClickHandler?.(id2, (event) => {
|
|
615081
|
+
const line = event.lineText ?? "";
|
|
615082
|
+
if (line.includes(VOICECHAT_EXPAND_LABEL) || line.includes(VOICECHAT_COLLAPSE_LABEL)) {
|
|
615083
|
+
state.expanded = !state.expanded;
|
|
615084
|
+
host.refreshDynamicBlocks?.();
|
|
615085
|
+
return true;
|
|
615086
|
+
}
|
|
615087
|
+
return false;
|
|
615088
|
+
});
|
|
615089
|
+
host.appendDynamicBlock(id2);
|
|
615090
|
+
}
|
|
615023
615091
|
function appendContextBoxSection(body, label, value2) {
|
|
615024
615092
|
const text2 = String(value2 ?? "").trim();
|
|
615025
615093
|
if (!text2) return;
|
|
@@ -615893,7 +615961,7 @@ function formatDuration4(ms) {
|
|
|
615893
615961
|
const secs = Math.floor(totalSecs % 60);
|
|
615894
615962
|
return `${mins}m ${secs}s`;
|
|
615895
615963
|
}
|
|
615896
|
-
var c3, ui, pastel, _emojisEnabled, _colorsEnabled, MD, TOOL_LABELS, TOOL_COLOR_CODES, TOOL_ERROR_COLOR_CODE, BOX_TL2, BOX_TR2, BOX_BL2, BOX_BR2, BOX_H2, BOX_V2, BOX_TJ_L2, BOX_TJ_R2, RESET2, _telegramCoalesce, _pendingToolCall, DIFF_LINE_RE, _contentWriteHook, HINTS, TOOL_NAMES, COMMAND_NAMES, SLASH_COMMANDS2;
|
|
615964
|
+
var c3, ui, pastel, _emojisEnabled, _colorsEnabled, MD, TOOL_LABELS, TOOL_COLOR_CODES, TOOL_ERROR_COLOR_CODE, BOX_TL2, BOX_TR2, BOX_BL2, BOX_BR2, BOX_H2, BOX_V2, BOX_TJ_L2, BOX_TJ_R2, RESET2, _telegramCoalesce, _pendingToolCall, _voiceChatCoalesce, VOICECHAT_VISIBLE_ROWS, VOICECHAT_EXPAND_LABEL, VOICECHAT_COLLAPSE_LABEL, DIFF_LINE_RE, _contentWriteHook, HINTS, TOOL_NAMES, COMMAND_NAMES, SLASH_COMMANDS2;
|
|
615897
615965
|
var init_render = __esm({
|
|
615898
615966
|
"packages/cli/src/tui/render.ts"() {
|
|
615899
615967
|
"use strict";
|
|
@@ -616054,6 +616122,10 @@ var init_render = __esm({
|
|
|
616054
616122
|
RESET2 = "\x1B[0m";
|
|
616055
616123
|
_telegramCoalesce = null;
|
|
616056
616124
|
_pendingToolCall = null;
|
|
616125
|
+
_voiceChatCoalesce = null;
|
|
616126
|
+
VOICECHAT_VISIBLE_ROWS = 20;
|
|
616127
|
+
VOICECHAT_EXPAND_LABEL = "earlier turns — click to read full transcript";
|
|
616128
|
+
VOICECHAT_COLLAPSE_LABEL = "full transcript — click to show recent only";
|
|
616057
616129
|
DIFF_LINE_RE = /^\s*\d+\s([-+])\s/;
|
|
616058
616130
|
_contentWriteHook = null;
|
|
616059
616131
|
HINTS = [
|
|
@@ -696657,7 +696729,7 @@ function buildRealtimeVoiceMessages(turns, maxMessages = MAX_REALTIME_MODEL_MESS
|
|
|
696657
696729
|
}
|
|
696658
696730
|
return [...pinned, ...windowed];
|
|
696659
696731
|
}
|
|
696660
|
-
var VAD_SILENCE_MS, MAX_SEGMENT_MS, SUMMARY_INJECTION_INTERVAL, MAX_CONTEXT_TURNS, MAX_REALTIME_MODEL_MESSAGES, CONTEXT_SNAPSHOT_PREFIX, MAX_VOICE_REPLY_CHARS, AGENT_ECHO_WINDOW_MS, AGENT_ECHO_SIMILARITY, SYSTEM_PROMPT2, MIN_SIGNAL_SCORE, NOISE_ONLY_RE, VoiceChatSession;
|
|
696732
|
+
var VAD_SILENCE_MS, MAX_SEGMENT_MS, SUMMARY_INJECTION_INTERVAL, MAX_CONTEXT_TURNS, MAX_REALTIME_MODEL_MESSAGES, CONTEXT_SNAPSHOT_PREFIX, CONTEXT_SNAPSHOT_SCOPE, MAX_VOICE_REPLY_CHARS, AGENT_ECHO_WINDOW_MS, AGENT_ECHO_SIMILARITY, SYSTEM_PROMPT2, MIN_SIGNAL_SCORE, NOISE_ONLY_RE, VoiceChatSession;
|
|
696661
696733
|
var init_voicechat = __esm({
|
|
696662
696734
|
"packages/cli/src/tui/voicechat.ts"() {
|
|
696663
696735
|
"use strict";
|
|
@@ -696666,7 +696738,8 @@ var init_voicechat = __esm({
|
|
|
696666
696738
|
SUMMARY_INJECTION_INTERVAL = 4;
|
|
696667
696739
|
MAX_CONTEXT_TURNS = 20;
|
|
696668
696740
|
MAX_REALTIME_MODEL_MESSAGES = 16;
|
|
696669
|
-
CONTEXT_SNAPSHOT_PREFIX = "
|
|
696741
|
+
CONTEXT_SNAPSHOT_PREFIX = "[ACTIVE CONTEXT FRAME]";
|
|
696742
|
+
CONTEXT_SNAPSHOT_SCOPE = "Scope: live session + perception state for your next reply. Treat this as the single merged context intake — background state, not a topic. Answer the user's latest message; do not narrate or re-emit this frame unless the user asks about it.";
|
|
696670
696743
|
MAX_VOICE_REPLY_CHARS = 700;
|
|
696671
696744
|
AGENT_ECHO_WINDOW_MS = 12e3;
|
|
696672
696745
|
AGENT_ECHO_SIMILARITY = 0.72;
|
|
@@ -696675,7 +696748,7 @@ var init_voicechat = __esm({
|
|
|
696675
696748
|
Rules:
|
|
696676
696749
|
- ALWAYS answer the user's most recent message directly. The live context snapshot is background perception — never the topic. Do not describe cameras, feeds, or surroundings unless the user's latest message asks about them.
|
|
696677
696750
|
- Never repeat your previous answer. If you already described the scene, don't describe it again unless explicitly asked again — respond to what the user just said.
|
|
696678
|
-
- Live perception: each turn you may receive a read-only "
|
|
696751
|
+
- Live perception: each turn you may receive a read-only "[ACTIVE CONTEXT FRAME]" system message containing what the camera currently sees (objects, people, classifications, recent visual events) and what is currently heard (sound scene, recent sounds, transcripts). Treat it as your own live sight and hearing. When the user asks what you SEE, answer from the camera fields; when the user asks what you HEAR, answer from the audio/sound fields. If the snapshot is missing or stale, say you can't see/hear clearly right now — do not invent observations.
|
|
696679
696752
|
- Never invent environment facts (cwd, OS, specs, repo state). If you need a precise fact from the main agent, request a tool by outputting on a single line EXACTLY one JSON object: {"tool": string, "args": object} and nothing else. Then wait for the tool result before answering.
|
|
696680
696753
|
- You may also request to relay a user task to the main agent by emitting {"tool":"voice_to_main","args":{"message":"...","start":true}}.
|
|
696681
696754
|
- Prefer tools for factual queries; otherwise, answer directly with a short reply.
|
|
@@ -696964,8 +697037,14 @@ Rules:
|
|
|
696964
697037
|
const snap = await Promise.resolve(this.toolRelay.contextSnapshot());
|
|
696965
697038
|
if (snap && snap.trim()) {
|
|
696966
697039
|
this.dropTransientSystemTurns();
|
|
696967
|
-
this.context.push({
|
|
696968
|
-
|
|
697040
|
+
this.context.push({
|
|
697041
|
+
role: "system",
|
|
697042
|
+
content: `${CONTEXT_SNAPSHOT_PREFIX}
|
|
697043
|
+
turn: ${this.turnCount}
|
|
697044
|
+
${CONTEXT_SNAPSHOT_SCOPE}
|
|
697045
|
+
|
|
697046
|
+
${snap.trim()}`
|
|
697047
|
+
});
|
|
696969
697048
|
this.trimContext();
|
|
696970
697049
|
}
|
|
696971
697050
|
} catch {
|
|
@@ -736406,14 +736485,16 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
|
|
|
736406
736485
|
writeContent(() => renderInfo(`[voicechat] ${msg}`));
|
|
736407
736486
|
},
|
|
736408
736487
|
onUserSpeech(text2) {
|
|
736409
|
-
writeContent(
|
|
736488
|
+
writeContent(
|
|
736489
|
+
() => renderVoiceChatCoalescedRow(`\x1B[38;5;45m[you]\x1B[0m ${text2}`)
|
|
736490
|
+
);
|
|
736410
736491
|
},
|
|
736411
736492
|
// Suppressed to keep main loop quiet
|
|
736412
736493
|
onPartialTranscript(_text) {
|
|
736413
736494
|
},
|
|
736414
736495
|
onAgentSpeech(text2) {
|
|
736415
736496
|
writeContent(
|
|
736416
|
-
() =>
|
|
736497
|
+
() => renderVoiceChatCoalescedRow(
|
|
736417
736498
|
`\x1B[38;5;37m[agent]\x1B[0m ${truncateByLines(text2, 5, 600)}`
|
|
736418
736499
|
)
|
|
736419
736500
|
);
|
|
@@ -736429,6 +736510,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
|
|
|
736429
736510
|
await _voiceChatSession2.stop();
|
|
736430
736511
|
_voiceChatSession2 = null;
|
|
736431
736512
|
}
|
|
736513
|
+
breakVoiceChatCoalesce();
|
|
736432
736514
|
},
|
|
736433
736515
|
isVoiceChatActive() {
|
|
736434
736516
|
return _voiceChatSession2?.isActive ?? false;
|
|
@@ -129,6 +129,44 @@ import numpy as np
|
|
|
129
129
|
# Main transcription loop
|
|
130
130
|
# ---------------------------------------------------------------------------
|
|
131
131
|
|
|
132
|
+
def _norm_tokens(text):
|
|
133
|
+
"""Normalize a transcript for cross-model comparison: lowercase, strip
|
|
134
|
+
punctuation, collapse whitespace, drop empty tokens."""
|
|
135
|
+
import re
|
|
136
|
+
return [t for t in re.sub(r"[^\w\s]", " ", text.lower()).split() if t]
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _texts_agree(a: str, b: str, threshold: float) -> bool:
|
|
140
|
+
"""Consensus check between two model transcripts. Real speech produces
|
|
141
|
+
similar output from different whisper models; hallucinations on noise are
|
|
142
|
+
high-variance and virtually never match across models."""
|
|
143
|
+
ta, tb = _norm_tokens(a), _norm_tokens(b)
|
|
144
|
+
if not ta or not tb:
|
|
145
|
+
return False
|
|
146
|
+
# Containment: short utterances often differ only by dropped filler words
|
|
147
|
+
ja, jb = " ".join(ta), " ".join(tb)
|
|
148
|
+
if ja in jb or jb in ja:
|
|
149
|
+
return True
|
|
150
|
+
import difflib
|
|
151
|
+
return difflib.SequenceMatcher(None, ta, tb).ratio() >= threshold
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _segment_filtered_text(result) -> str:
|
|
155
|
+
"""Rebuild transcript from segments, dropping low-confidence and
|
|
156
|
+
likely-non-speech segments (classic hallucination carriers)."""
|
|
157
|
+
segments = result.get("segments") or []
|
|
158
|
+
if not segments:
|
|
159
|
+
return result.get("text", "").strip()
|
|
160
|
+
kept = []
|
|
161
|
+
for seg in segments:
|
|
162
|
+
if float(seg.get("no_speech_prob", 0.0)) > 0.6:
|
|
163
|
+
continue
|
|
164
|
+
if float(seg.get("avg_logprob", 0.0)) < -1.2:
|
|
165
|
+
continue
|
|
166
|
+
kept.append(str(seg.get("text", "")))
|
|
167
|
+
return " ".join(part.strip() for part in kept if part.strip()).strip()
|
|
168
|
+
|
|
169
|
+
|
|
132
170
|
def main():
|
|
133
171
|
import argparse
|
|
134
172
|
parser = argparse.ArgumentParser(description="Live Whisper transcription worker")
|
|
@@ -136,6 +174,10 @@ def main():
|
|
|
136
174
|
parser.add_argument("--chunk-seconds", type=float, default=CHUNK_SECONDS, help="Transcribe interval")
|
|
137
175
|
parser.add_argument("--window-seconds", type=float, default=WINDOW_SECONDS, help="Sliding window size")
|
|
138
176
|
parser.add_argument("--language", default=None, help="Language code (e.g. en, es, fr). Auto-detect if omitted.")
|
|
177
|
+
parser.add_argument("--consensus-model", default="tiny",
|
|
178
|
+
help="Second whisper model run in parallel on the same audio; transcripts are only emitted when both models agree. 'off' disables.")
|
|
179
|
+
parser.add_argument("--consensus-threshold", type=float, default=0.55,
|
|
180
|
+
help="Token similarity (0-1) required between the two models' transcripts.")
|
|
139
181
|
args = parser.parse_args()
|
|
140
182
|
|
|
141
183
|
import whisper
|
|
@@ -156,6 +198,19 @@ def main():
|
|
|
156
198
|
emit_error(f"Failed to load model: {e}")
|
|
157
199
|
sys.exit(1)
|
|
158
200
|
|
|
201
|
+
# Consensus model: a second (usually smaller) model transcribes the same
|
|
202
|
+
# window in parallel. Hallucinations (mixed-language garbage on noisy or
|
|
203
|
+
# quiet audio) don't reproduce across models, so disagreement = reject.
|
|
204
|
+
consensus_model = None
|
|
205
|
+
consensus_name = (args.consensus_model or "off").strip().lower()
|
|
206
|
+
if consensus_name not in ("off", "none", "", args.model):
|
|
207
|
+
emit_status(f"Loading consensus Whisper {consensus_name} model...")
|
|
208
|
+
try:
|
|
209
|
+
consensus_model = whisper.load_model(consensus_name, device=device)
|
|
210
|
+
except Exception as e:
|
|
211
|
+
emit_status(f"Consensus model unavailable ({e}); running single-model.")
|
|
212
|
+
consensus_model = None
|
|
213
|
+
|
|
159
214
|
emit({"type": "ready"})
|
|
160
215
|
|
|
161
216
|
# Audio buffer — accumulates PCM16 as float32 @ 16kHz
|
|
@@ -235,17 +290,48 @@ def main():
|
|
|
235
290
|
# Amplify toward full scale before transcription
|
|
236
291
|
window = amplify(window)
|
|
237
292
|
|
|
238
|
-
# Transcribe
|
|
293
|
+
# Transcribe — primary and consensus models in parallel threads
|
|
294
|
+
# (torch releases the GIL during compute, so this is real
|
|
295
|
+
# parallelism on multicore ARM).
|
|
239
296
|
try:
|
|
240
297
|
fp16 = (device == "cuda")
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
298
|
+
|
|
299
|
+
def run_transcribe(m):
|
|
300
|
+
return m.transcribe(
|
|
301
|
+
window,
|
|
302
|
+
fp16=fp16,
|
|
303
|
+
language=args.language,
|
|
304
|
+
no_speech_threshold=0.6,
|
|
305
|
+
condition_on_previous_text=False,
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
if consensus_model is not None:
|
|
309
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
310
|
+
with ThreadPoolExecutor(max_workers=2) as pool:
|
|
311
|
+
primary_future = pool.submit(run_transcribe, model)
|
|
312
|
+
secondary_future = pool.submit(run_transcribe, consensus_model)
|
|
313
|
+
result = primary_future.result()
|
|
314
|
+
secondary = secondary_future.result()
|
|
315
|
+
else:
|
|
316
|
+
result = run_transcribe(model)
|
|
317
|
+
secondary = None
|
|
318
|
+
|
|
319
|
+
text = _segment_filtered_text(result)
|
|
320
|
+
|
|
321
|
+
if text and secondary is not None:
|
|
322
|
+
secondary_text = _segment_filtered_text(secondary)
|
|
323
|
+
lang_a = str(result.get("language", "")).lower()
|
|
324
|
+
lang_b = str(secondary.get("language", "")).lower()
|
|
325
|
+
lang_disagree = bool(lang_a and lang_b and lang_a != lang_b and not args.language)
|
|
326
|
+
if lang_disagree or not _texts_agree(text, secondary_text, args.consensus_threshold):
|
|
327
|
+
emit({
|
|
328
|
+
"type": "consensus_rejected",
|
|
329
|
+
"primary": text[:200],
|
|
330
|
+
"secondary": (secondary_text or "")[:200],
|
|
331
|
+
"reason": "language_mismatch" if lang_disagree else "low_similarity",
|
|
332
|
+
})
|
|
333
|
+
continue
|
|
334
|
+
|
|
249
335
|
if text and text != last_text:
|
|
250
336
|
last_text = text
|
|
251
337
|
emit_transcript(text, is_final=False)
|
|
@@ -270,7 +356,7 @@ def main():
|
|
|
270
356
|
fp16=fp16,
|
|
271
357
|
language=args.language,
|
|
272
358
|
)
|
|
273
|
-
text = result
|
|
359
|
+
text = _segment_filtered_text(result)
|
|
274
360
|
if text:
|
|
275
361
|
emit_transcript(text, is_final=True)
|
|
276
362
|
except Exception:
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omnius",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.421",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "omnius",
|
|
9
|
-
"version": "1.0.
|
|
9
|
+
"version": "1.0.421",
|
|
10
10
|
"bundleDependencies": [
|
|
11
11
|
"image-to-ascii"
|
|
12
12
|
],
|
|
@@ -2505,9 +2505,9 @@
|
|
|
2505
2505
|
}
|
|
2506
2506
|
},
|
|
2507
2507
|
"node_modules/bare-fs": {
|
|
2508
|
-
"version": "4.7.
|
|
2509
|
-
"resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.
|
|
2510
|
-
"integrity": "sha512-
|
|
2508
|
+
"version": "4.7.3",
|
|
2509
|
+
"resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.3.tgz",
|
|
2510
|
+
"integrity": "sha512-xRgplks8SvcKkdlv2M6Z2LZmRsmqd+x0nXXGXeMEjwdibj1HSDrlnqBRLeYdMvsgCox7Bq0e+DHwfczOfsn6IA==",
|
|
2511
2511
|
"license": "Apache-2.0",
|
|
2512
2512
|
"optional": true,
|
|
2513
2513
|
"dependencies": {
|
package/package.json
CHANGED