77import httpx
88
99from src .config import AppConfig , ProviderConfig , parse_custom_headers
10- from src .utils import AppError , PipelineResult , ScreamerError
10+ from src .utils import AppError , PipelineResult , ScreamerError , log_duration
1111
1212log = logging .getLogger (__name__ )
1313
@@ -23,37 +23,38 @@ def transcribe(audio_wav: bytes, config: AppConfig) -> PipelineResult:
2323
2424 *config* supplies primary and fallback providers via ``AppConfig``.
2525 """
26- warnings : list [AppError ] = []
27-
28- primary = config .stt_provider ()
29- fallback = config .stt_fallback_provider ()
30-
31- if not primary .is_complete and not fallback .is_complete :
32- raise ScreamerError (AppError .STT_FAILED , "No STT API key configured" )
33-
34- for is_fallback , provider , language in (
35- (False , primary , config .stt_language ),
36- (True , fallback .provider , "" ),
37- ):
38- if is_fallback and not fallback .enabled :
39- continue
40- if not provider .is_complete :
41- continue
42-
43- try :
44- text = _call_stt (provider = provider , language = language , audio_wav = audio_wav )
45- if text is not None :
46- if is_fallback :
47- warnings .append (AppError .STT_FALLBACK_USED )
48- return PipelineResult (text = text , warnings = warnings )
49- except ScreamerError :
50- raise
51- except Exception as e :
52- log .warning ("%s STT failed: %s" , "Fallback" if is_fallback else "Primary" , e )
53- if not fallback .enabled :
54- raise ScreamerError (AppError .STT_FAILED , str (e )) from e
55-
56- raise ScreamerError (AppError .STT_FAILED , "Both primary and fallback STT failed or returned no speech" )
26+ with log_duration (log , "STT transcription" ):
27+ warnings : list [AppError ] = []
28+
29+ primary = config .stt_provider ()
30+ fallback = config .stt_fallback_provider ()
31+
32+ if not primary .is_complete and not fallback .is_complete :
33+ raise ScreamerError (AppError .STT_FAILED , "No STT API key configured" )
34+
35+ for is_fallback , provider , language in (
36+ (False , primary , config .stt_language ),
37+ (True , fallback .provider , "" ),
38+ ):
39+ if is_fallback and not fallback .enabled :
40+ continue
41+ if not provider .is_complete :
42+ continue
43+
44+ try :
45+ text = _call_stt (provider = provider , language = language , audio_wav = audio_wav )
46+ if text is not None :
47+ if is_fallback :
48+ warnings .append (AppError .STT_FALLBACK_USED )
49+ return PipelineResult (text = text , warnings = warnings )
50+ except ScreamerError :
51+ raise
52+ except Exception as e :
53+ log .warning ("%s STT failed: %s" , "Fallback" if is_fallback else "Primary" , e )
54+ if not fallback .enabled :
55+ raise ScreamerError (AppError .STT_FAILED , str (e )) from e
56+
57+ raise ScreamerError (AppError .STT_FAILED , "Both primary and fallback STT failed or returned no speech" )
5758
5859
5960def _call_stt (
@@ -79,26 +80,27 @@ def _call_stt(
7980
8081 files = {"file" : ("recording.wav" , audio_wav , "audio/wav" )}
8182
82- log .info ("STT request: url=%s model=%s" , url , provider .model )
83- resp = httpx .post (url , headers = headers , data = data , files = files , timeout = 60.0 )
84- resp .raise_for_status ()
83+ with log_duration (log , f"STT request ({ provider .model } )" ):
84+ log .info ("STT request: url=%s model=%s" , url , provider .model )
85+ resp = httpx .post (url , headers = headers , data = data , files = files , timeout = 60.0 )
86+ resp .raise_for_status ()
8587
86- result = resp .json ()
87- segments = result .get ("segments" , [])
88+ result = resp .json ()
89+ segments = result .get ("segments" , [])
8890
89- # Filter: keep if ANY segment has no_speech_prob < threshold.
90- if segments :
91- has_speech = any (seg .get ("no_speech_prob" , 0.0 ) < _NO_SPEECH_THRESHOLD for seg in segments )
92- if not has_speech :
93- log .debug ("All segments above no_speech_prob threshold; filtering out" )
94- raise ScreamerError (AppError .NO_SPEECH )
91+ # Filter: keep if ANY segment has no_speech_prob < threshold.
92+ if segments :
93+ has_speech = any (seg .get ("no_speech_prob" , 0.0 ) < _NO_SPEECH_THRESHOLD for seg in segments )
94+ if not has_speech :
95+ log .debug ("All segments above no_speech_prob threshold; filtering out" )
96+ raise ScreamerError (AppError .NO_SPEECH )
9597
96- text = (result .get ("text" ) or "" ).strip ()
97- if not text :
98- raise ScreamerError (AppError .NO_SPEECH )
98+ text = (result .get ("text" ) or "" ).strip ()
99+ if not text :
100+ raise ScreamerError (AppError .NO_SPEECH )
99101
100- log .debug ("STT result: %s" , text [:80 ])
101- return text
102+ log .debug ("STT result: %s" , text [:80 ])
103+ return text
102104
103105
104106# ---------------------------------------------------------------------------
0 commit comments