@@ -30,6 +30,13 @@ use crate::{
3030pub const WHISPER_BACKEND_ID : & str = "whisper-cpp" ;
3131const WHISPER_SAMPLE_RATE : u32 = 16_000 ;
3232const CHUNK_SAMPLES : usize = 16_000 * 12 ;
33+ /// Whisper's default threshold for treating a decoded segment as silence.
34+ ///
35+ /// whisper.cpp exposes this probability on every segment but does not apply
36+ /// the threshold itself. Filtering here prevents text hallucinated from
37+ /// silence (for example common outro phrases) from reaching transcript
38+ /// consumers.
39+ const NO_SPEECH_PROBABILITY_THRESHOLD : f32 = 0.6 ;
3340
3441/// Factory registration for the built-in whisper.cpp provider.
3542#[ derive( Debug , Default , Clone , Copy ) ]
@@ -705,6 +712,9 @@ fn transcribe_chunk(
705712 . map_err ( |error| format ! ( "Whisper inference failed: {error}" ) ) ?;
706713 let offset_seconds = * offset_samples as f64 / f64:: from ( WHISPER_SAMPLE_RATE ) ;
707714 for segment in state. as_iter ( ) {
715+ if is_probable_no_speech ( segment. no_speech_probability ( ) ) {
716+ continue ;
717+ }
708718 let text = segment
709719 . to_str_lossy ( )
710720 . map_err ( |error| format ! ( "Whisper returned invalid text: {error}" ) ) ?
@@ -734,6 +744,10 @@ fn transcribe_chunk(
734744 Ok ( ( ) )
735745}
736746
747+ fn is_probable_no_speech ( probability : f32 ) -> bool {
748+ probability. is_finite ( ) && probability > NO_SPEECH_PROBABILITY_THRESHOLD
749+ }
750+
737751#[ cfg( test) ]
738752mod tests {
739753 use std:: time:: Duration ;
@@ -749,10 +763,27 @@ mod tests {
749763 } ;
750764
751765 use super :: {
752- StreamingResampler , WHISPER_BACKEND_ID , WhisperTranscriberBackend ,
753- WhisperTranscriberFactory , frame_to_mono_samples, model_file_ready, whisper_language,
766+ NO_SPEECH_PROBABILITY_THRESHOLD , StreamingResampler , WHISPER_BACKEND_ID ,
767+ WhisperTranscriberBackend , WhisperTranscriberFactory , frame_to_mono_samples,
768+ is_probable_no_speech, model_file_ready, whisper_language,
754769 } ;
755770
771+ #[ test]
772+ fn high_no_speech_probability_is_filtered_as_hallucination ( ) {
773+ assert ! ( is_probable_no_speech(
774+ NO_SPEECH_PROBABILITY_THRESHOLD + f32 :: EPSILON
775+ ) ) ;
776+ assert ! ( is_probable_no_speech( 1.0 ) ) ;
777+ }
778+
779+ #[ test]
780+ fn speech_and_invalid_probabilities_are_not_filtered ( ) {
781+ assert ! ( !is_probable_no_speech( NO_SPEECH_PROBABILITY_THRESHOLD ) ) ;
782+ assert ! ( !is_probable_no_speech( 0.0 ) ) ;
783+ assert ! ( !is_probable_no_speech( f32 :: NAN ) ) ;
784+ assert ! ( !is_probable_no_speech( f32 :: INFINITY ) ) ;
785+ }
786+
756787 #[ test]
757788 fn locale_maps_to_whisper_language ( ) {
758789 assert_eq ! ( whisper_language( "ja-JP" ) . as_deref( ) , Some ( "ja" ) ) ;
0 commit comments