omnius 1.0.443 → 1.0.445
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 +592 -164
- package/dist/scripts/live-whisper.py +83 -10
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
|
@@ -14,8 +14,8 @@ Architecture (EGG voice/whisper lineage):
|
|
|
14
14
|
(USB 2886:0018), the XMOS DSP's SPEECHDETECTED register gates utterances,
|
|
15
15
|
on-chip echo cancellation is enabled, and the DOA angle is attached to
|
|
16
16
|
transcripts. Falls back to adaptive energy VAD otherwise.
|
|
17
|
-
-
|
|
18
|
-
utterances
|
|
17
|
+
- Optional dual-model consensus: a second whisper model can validate
|
|
18
|
+
finalized utterances when explicitly enabled.
|
|
19
19
|
|
|
20
20
|
Protocol:
|
|
21
21
|
stdin — raw PCM16 audio (16kHz, mono, 16-bit signed LE)
|
|
@@ -58,6 +58,23 @@ SAMPLE_WIDTH = 2 # 16-bit
|
|
|
58
58
|
BLOCK_SECONDS = 0.2
|
|
59
59
|
BLOCK_SAMPLES = int(SAMPLE_RATE * BLOCK_SECONDS)
|
|
60
60
|
|
|
61
|
+
|
|
62
|
+
def env_float(name: str, default: float) -> float:
|
|
63
|
+
raw = os.environ.get(name, "").strip()
|
|
64
|
+
if not raw:
|
|
65
|
+
return default
|
|
66
|
+
try:
|
|
67
|
+
return float(raw)
|
|
68
|
+
except ValueError:
|
|
69
|
+
return default
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def env_bool(name: str, default: bool = False) -> bool:
|
|
73
|
+
raw = os.environ.get(name, "").strip().lower()
|
|
74
|
+
if not raw:
|
|
75
|
+
return default
|
|
76
|
+
return raw in ("1", "true", "yes", "on", "enabled")
|
|
77
|
+
|
|
61
78
|
# ---------------------------------------------------------------------------
|
|
62
79
|
# Output helpers (JSON lines to stdout)
|
|
63
80
|
# ---------------------------------------------------------------------------
|
|
@@ -450,7 +467,7 @@ def main():
|
|
|
450
467
|
parser.add_argument("--chunk-seconds", type=float, default=3, help=argparse.SUPPRESS)
|
|
451
468
|
parser.add_argument("--window-seconds", type=float, default=10, help=argparse.SUPPRESS)
|
|
452
469
|
parser.add_argument("--language", default=None, help="Language code (e.g. en, es, fr). Auto-detect if omitted.")
|
|
453
|
-
parser.add_argument("--consensus-model", default="
|
|
470
|
+
parser.add_argument("--consensus-model", default="off",
|
|
454
471
|
help="Second whisper model run in parallel on finalized utterances; transcripts are only emitted when both models agree. 'off' disables.")
|
|
455
472
|
parser.add_argument("--consensus-threshold", type=float, default=0.55,
|
|
456
473
|
help="Token similarity (0-1) required between the two models' transcripts.")
|
|
@@ -460,6 +477,18 @@ def main():
|
|
|
460
477
|
help="Audio kept from before speech onset.")
|
|
461
478
|
parser.add_argument("--max-utterance-seconds", type=float, default=22,
|
|
462
479
|
help="Force-finalize utterances longer than this.")
|
|
480
|
+
parser.add_argument("--partials", action="store_true", default=env_bool("OMNIUS_ASR_PARTIALS", False),
|
|
481
|
+
help="Emit interim Whisper transcripts during long utterances. Disabled by default to avoid silence/noise hallucinations.")
|
|
482
|
+
parser.add_argument("--min-speech-ms", type=float, default=env_float("OMNIUS_ASR_MIN_SPEECH_MS", 450.0),
|
|
483
|
+
help="Minimum VAD-positive speech duration required before an utterance is sent to Whisper.")
|
|
484
|
+
parser.add_argument("--start-rms", type=float, default=env_float("OMNIUS_ASR_START_RMS", 0.0025),
|
|
485
|
+
help="Minimum block RMS required for software VAD speech start.")
|
|
486
|
+
parser.add_argument("--energy-ratio", type=float, default=env_float("OMNIUS_ASR_ENERGY_RATIO", 3.5),
|
|
487
|
+
help="Software VAD speech gate as a multiple of the adaptive noise floor.")
|
|
488
|
+
parser.add_argument("--min-utterance-rms", type=float, default=env_float("OMNIUS_ASR_MIN_UTTERANCE_RMS", 0.003),
|
|
489
|
+
help="Minimum loud-block RMS required before final transcription.")
|
|
490
|
+
parser.add_argument("--min-utterance-peak", type=float, default=env_float("OMNIUS_ASR_MIN_UTTERANCE_PEAK", 0.015),
|
|
491
|
+
help="Minimum utterance peak amplitude required when RMS is low.")
|
|
463
492
|
args = parser.parse_args()
|
|
464
493
|
|
|
465
494
|
import whisper
|
|
@@ -543,6 +572,9 @@ def main():
|
|
|
543
572
|
utterance = [] # list of float32 blocks while capturing
|
|
544
573
|
capturing = False
|
|
545
574
|
silence_run = 0
|
|
575
|
+
speech_blocks = 0
|
|
576
|
+
utterance_rms_values = []
|
|
577
|
+
utterance_peak = 0.0
|
|
546
578
|
noise_floor = None
|
|
547
579
|
last_partial_at = 0.0
|
|
548
580
|
last_final_text = ""
|
|
@@ -558,7 +590,7 @@ def main():
|
|
|
558
590
|
noise_floor = 0.7 * noise_floor + 0.3 * block_rms # drop fast
|
|
559
591
|
else:
|
|
560
592
|
noise_floor = 0.995 * noise_floor + 0.005 * block_rms # creep up slowly
|
|
561
|
-
gate = max(
|
|
593
|
+
gate = max(float(args.start_rms), noise_floor * float(args.energy_ratio))
|
|
562
594
|
return block_rms >= gate
|
|
563
595
|
|
|
564
596
|
def transcribe_partial(samples):
|
|
@@ -574,9 +606,31 @@ def main():
|
|
|
574
606
|
except Exception as e:
|
|
575
607
|
emit_error(f"Transcription error: {e}")
|
|
576
608
|
|
|
577
|
-
def
|
|
578
|
-
nonlocal last_final_text
|
|
609
|
+
def utterance_quality_ok(samples, speech_block_count, rms_values, peak) -> bool:
|
|
579
610
|
if len(samples) < int(0.4 * SAMPLE_RATE):
|
|
611
|
+
return False
|
|
612
|
+
speech_ms = speech_block_count * BLOCK_SECONDS * 1000.0
|
|
613
|
+
if speech_ms < float(args.min_speech_ms):
|
|
614
|
+
if env_bool("OMNIUS_ASR_VERBOSE_VAD", False):
|
|
615
|
+
emit_status(f"Dropped short VAD blob before Whisper: speech_ms={speech_ms:.0f}")
|
|
616
|
+
return False
|
|
617
|
+
if rms_values:
|
|
618
|
+
ordered = sorted(float(v) for v in rms_values)
|
|
619
|
+
loud_half = ordered[len(ordered) // 2:] or ordered
|
|
620
|
+
loud_rms = loud_half[len(loud_half) // 2]
|
|
621
|
+
else:
|
|
622
|
+
loud_rms = float(np.sqrt(np.mean(samples ** 2))) if len(samples) else 0.0
|
|
623
|
+
if loud_rms < float(args.min_utterance_rms) and peak < float(args.min_utterance_peak):
|
|
624
|
+
if env_bool("OMNIUS_ASR_VERBOSE_VAD", False):
|
|
625
|
+
emit_status(
|
|
626
|
+
f"Dropped low-energy VAD blob before Whisper: loud_rms={loud_rms:.5f} peak={peak:.5f}"
|
|
627
|
+
)
|
|
628
|
+
return False
|
|
629
|
+
return True
|
|
630
|
+
|
|
631
|
+
def transcribe_final(samples, speech_block_count=0, rms_values=None, peak=0.0):
|
|
632
|
+
nonlocal last_final_text
|
|
633
|
+
if not utterance_quality_ok(samples, speech_block_count, rms_values or [], peak):
|
|
580
634
|
return
|
|
581
635
|
samples = amplify(samples)
|
|
582
636
|
try:
|
|
@@ -612,7 +666,9 @@ def main():
|
|
|
612
666
|
last_final_text = primary_text
|
|
613
667
|
emit_transcript(primary_text, is_final=True, doa=primary_doa, consensus=True)
|
|
614
668
|
except Exception as e:
|
|
615
|
-
|
|
669
|
+
emit_consensus_rejected(primary_text, "", "validator_error")
|
|
670
|
+
if env_bool("OMNIUS_ASR_VERBOSE_CONSENSUS", False):
|
|
671
|
+
emit_status(f"Consensus validation error: {e}")
|
|
616
672
|
|
|
617
673
|
threading.Thread(
|
|
618
674
|
target=validate_consensus,
|
|
@@ -664,12 +720,18 @@ def main():
|
|
|
664
720
|
if speech:
|
|
665
721
|
capturing = True
|
|
666
722
|
silence_run = 0
|
|
723
|
+
speech_blocks = 1
|
|
724
|
+
utterance_rms_values = [block_rms]
|
|
725
|
+
utterance_peak = float(np.max(np.abs(block))) if len(block) else 0.0
|
|
667
726
|
utterance = list(preroll)
|
|
668
727
|
preroll.clear()
|
|
669
728
|
continue
|
|
670
729
|
|
|
671
730
|
utterance.append(block)
|
|
731
|
+
utterance_rms_values.append(block_rms)
|
|
732
|
+
utterance_peak = max(utterance_peak, float(np.max(np.abs(block))) if len(block) else 0.0)
|
|
672
733
|
if speech:
|
|
734
|
+
speech_blocks += 1
|
|
673
735
|
silence_run = 0
|
|
674
736
|
else:
|
|
675
737
|
silence_run += 1
|
|
@@ -677,11 +739,17 @@ def main():
|
|
|
677
739
|
utter_samples = sum(len(b) for b in utterance)
|
|
678
740
|
if silence_run >= finalize_blocks or utter_samples >= max_utter_samples:
|
|
679
741
|
samples = np.concatenate(utterance)
|
|
742
|
+
final_speech_blocks = speech_blocks
|
|
743
|
+
final_rms_values = list(utterance_rms_values)
|
|
744
|
+
final_peak = utterance_peak
|
|
680
745
|
capturing = False
|
|
681
746
|
utterance = []
|
|
682
747
|
silence_run = 0
|
|
683
|
-
|
|
684
|
-
|
|
748
|
+
speech_blocks = 0
|
|
749
|
+
utterance_rms_values = []
|
|
750
|
+
utterance_peak = 0.0
|
|
751
|
+
transcribe_final(samples, final_speech_blocks, final_rms_values, final_peak)
|
|
752
|
+
elif args.partials and utter_samples >= int(3.5 * SAMPLE_RATE) and now - last_partial_at >= 2.5:
|
|
685
753
|
last_partial_at = now
|
|
686
754
|
transcribe_partial(np.concatenate(utterance))
|
|
687
755
|
except KeyboardInterrupt:
|
|
@@ -690,7 +758,12 @@ def main():
|
|
|
690
758
|
# EOF: finalize any in-flight utterance.
|
|
691
759
|
if utterance:
|
|
692
760
|
try:
|
|
693
|
-
transcribe_final(
|
|
761
|
+
transcribe_final(
|
|
762
|
+
np.concatenate(utterance),
|
|
763
|
+
speech_blocks,
|
|
764
|
+
list(utterance_rms_values),
|
|
765
|
+
utterance_peak,
|
|
766
|
+
)
|
|
694
767
|
except Exception:
|
|
695
768
|
pass
|
|
696
769
|
if tuning is not None:
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omnius",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.445",
|
|
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.445",
|
|
10
10
|
"bundleDependencies": [
|
|
11
11
|
"image-to-ascii"
|
|
12
12
|
],
|
package/package.json
CHANGED