Merge listen-robustness: transcription retries + missing-mic message
This commit is contained in:
commit
177bf9f7cc
@ -217,6 +217,27 @@ async def _force_pwplay_volume_100() -> None:
|
||||
return
|
||||
|
||||
|
||||
async def _terminate_quietly(proc) -> None:
|
||||
"""Stop a recorder subprocess that may have ALREADY exited.
|
||||
|
||||
When the mic is disconnected, pw-record exits instantly — a later
|
||||
proc.terminate() then raises ProcessLookupError, which otherwise masks the
|
||||
friendly "no audio captured / is a mic connected?" error. Swallow that.
|
||||
"""
|
||||
try:
|
||||
proc.terminate()
|
||||
except ProcessLookupError:
|
||||
return
|
||||
try:
|
||||
await asyncio.wait_for(proc.wait(), timeout=2.0)
|
||||
except asyncio.TimeoutError:
|
||||
try:
|
||||
proc.kill()
|
||||
except ProcessLookupError:
|
||||
return
|
||||
await proc.wait()
|
||||
|
||||
|
||||
async def record_audio_until_silence(
|
||||
out_path: Path,
|
||||
silence_threshold_ms: int = 1500,
|
||||
@ -322,12 +343,7 @@ async def record_audio_until_silence(
|
||||
# The inner read returned no data — break outer loop
|
||||
break
|
||||
finally:
|
||||
proc.terminate()
|
||||
try:
|
||||
await asyncio.wait_for(proc.wait(), timeout=2.0)
|
||||
except asyncio.TimeoutError:
|
||||
proc.kill()
|
||||
await proc.wait()
|
||||
await _terminate_quietly(proc)
|
||||
|
||||
if not pcm_buf:
|
||||
raise PlaybackError(
|
||||
@ -414,14 +430,12 @@ async def record_audio(
|
||||
# If we get here, pw-record exited early (mic gone, permission, etc.)
|
||||
except asyncio.TimeoutError:
|
||||
# Expected path — stop recording cleanly.
|
||||
proc.terminate()
|
||||
try:
|
||||
await asyncio.wait_for(proc.wait(), timeout=2.0)
|
||||
except asyncio.TimeoutError:
|
||||
proc.kill()
|
||||
await proc.wait()
|
||||
await _terminate_quietly(proc)
|
||||
except asyncio.CancelledError:
|
||||
proc.kill()
|
||||
try:
|
||||
proc.kill()
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
await proc.wait()
|
||||
raise
|
||||
|
||||
@ -482,14 +496,12 @@ async def _record_fixed_stream(
|
||||
try:
|
||||
await asyncio.wait_for(_drain(), timeout=duration_seconds)
|
||||
except asyncio.TimeoutError:
|
||||
proc.terminate()
|
||||
try:
|
||||
await asyncio.wait_for(proc.wait(), timeout=2.0)
|
||||
except asyncio.TimeoutError:
|
||||
proc.kill()
|
||||
await proc.wait()
|
||||
await _terminate_quietly(proc)
|
||||
except asyncio.CancelledError:
|
||||
proc.kill()
|
||||
try:
|
||||
proc.kill()
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
await proc.wait()
|
||||
raise
|
||||
|
||||
|
||||
@ -43,6 +43,10 @@ OUTPUT_DIR_CONTAINER = Path("/output")
|
||||
# the discipline _resolve_output_path uses for writes.
|
||||
_TRANSCRIBE_INPUT_DIRS = (Path("/output"), Path("/tmp/mcspeak"))
|
||||
|
||||
# listen() retries transcription this many times (the Parakeet gateway
|
||||
# intermittently times out; the recording is saved, so retrying is free).
|
||||
_LISTEN_TRANSCRIBE_ATTEMPTS = 3
|
||||
|
||||
|
||||
def _validate_readable_audio_path(audio_path: str) -> Path:
|
||||
"""Resolve audio_path against the allowed input dirs and check existence.
|
||||
@ -893,19 +897,37 @@ async def listen(
|
||||
await ctx.info(f"Recording saved failed (non-fatal): {e}")
|
||||
|
||||
await ctx.info(f"Transcribing {rec_path.name}...")
|
||||
try:
|
||||
result = await transcribe_audio(
|
||||
rec_path,
|
||||
response_format=response_format,
|
||||
timestamp_granularities=timestamp_granularities,
|
||||
diarize=diarize,
|
||||
num_speakers=num_speakers,
|
||||
punctuation=punctuation,
|
||||
min_confidence=min_confidence,
|
||||
)
|
||||
except TranscriptionError as e:
|
||||
# The Parakeet gateway serializes inference on one slot and intermittently
|
||||
# times out when the backend is busy/cold. The recording is already saved,
|
||||
# so retry a couple of times before giving up rather than making the caller
|
||||
# re-run transcribe() by hand.
|
||||
result = None
|
||||
last_err: TranscriptionError | None = None
|
||||
for attempt in range(_LISTEN_TRANSCRIBE_ATTEMPTS):
|
||||
try:
|
||||
result = await transcribe_audio(
|
||||
rec_path,
|
||||
response_format=response_format,
|
||||
timestamp_granularities=timestamp_granularities,
|
||||
diarize=diarize,
|
||||
num_speakers=num_speakers,
|
||||
punctuation=punctuation,
|
||||
min_confidence=min_confidence,
|
||||
)
|
||||
break
|
||||
except TranscriptionError as e:
|
||||
last_err = e
|
||||
if attempt < _LISTEN_TRANSCRIBE_ATTEMPTS - 1:
|
||||
await ctx.info(
|
||||
f"Transcription attempt {attempt + 1} failed ({e}); retrying..."
|
||||
)
|
||||
await asyncio.sleep(0.75)
|
||||
if result is None:
|
||||
return {
|
||||
"error": str(e),
|
||||
"error": (
|
||||
f"Transcription failed after {_LISTEN_TRANSCRIBE_ATTEMPTS} attempts: "
|
||||
f"{last_err}"
|
||||
),
|
||||
"recorded": str(rec_path),
|
||||
"saved_to": saved_to,
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user