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
|
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(
|
async def record_audio_until_silence(
|
||||||
out_path: Path,
|
out_path: Path,
|
||||||
silence_threshold_ms: int = 1500,
|
silence_threshold_ms: int = 1500,
|
||||||
@ -322,12 +343,7 @@ async def record_audio_until_silence(
|
|||||||
# The inner read returned no data — break outer loop
|
# The inner read returned no data — break outer loop
|
||||||
break
|
break
|
||||||
finally:
|
finally:
|
||||||
proc.terminate()
|
await _terminate_quietly(proc)
|
||||||
try:
|
|
||||||
await asyncio.wait_for(proc.wait(), timeout=2.0)
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
proc.kill()
|
|
||||||
await proc.wait()
|
|
||||||
|
|
||||||
if not pcm_buf:
|
if not pcm_buf:
|
||||||
raise PlaybackError(
|
raise PlaybackError(
|
||||||
@ -414,14 +430,12 @@ async def record_audio(
|
|||||||
# If we get here, pw-record exited early (mic gone, permission, etc.)
|
# If we get here, pw-record exited early (mic gone, permission, etc.)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
# Expected path — stop recording cleanly.
|
# Expected path — stop recording cleanly.
|
||||||
proc.terminate()
|
await _terminate_quietly(proc)
|
||||||
try:
|
|
||||||
await asyncio.wait_for(proc.wait(), timeout=2.0)
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
proc.kill()
|
|
||||||
await proc.wait()
|
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
proc.kill()
|
try:
|
||||||
|
proc.kill()
|
||||||
|
except ProcessLookupError:
|
||||||
|
pass
|
||||||
await proc.wait()
|
await proc.wait()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@ -482,14 +496,12 @@ async def _record_fixed_stream(
|
|||||||
try:
|
try:
|
||||||
await asyncio.wait_for(_drain(), timeout=duration_seconds)
|
await asyncio.wait_for(_drain(), timeout=duration_seconds)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
proc.terminate()
|
await _terminate_quietly(proc)
|
||||||
try:
|
|
||||||
await asyncio.wait_for(proc.wait(), timeout=2.0)
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
proc.kill()
|
|
||||||
await proc.wait()
|
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
proc.kill()
|
try:
|
||||||
|
proc.kill()
|
||||||
|
except ProcessLookupError:
|
||||||
|
pass
|
||||||
await proc.wait()
|
await proc.wait()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|||||||
@ -43,6 +43,10 @@ OUTPUT_DIR_CONTAINER = Path("/output")
|
|||||||
# the discipline _resolve_output_path uses for writes.
|
# the discipline _resolve_output_path uses for writes.
|
||||||
_TRANSCRIBE_INPUT_DIRS = (Path("/output"), Path("/tmp/mcspeak"))
|
_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:
|
def _validate_readable_audio_path(audio_path: str) -> Path:
|
||||||
"""Resolve audio_path against the allowed input dirs and check existence.
|
"""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"Recording saved failed (non-fatal): {e}")
|
||||||
|
|
||||||
await ctx.info(f"Transcribing {rec_path.name}...")
|
await ctx.info(f"Transcribing {rec_path.name}...")
|
||||||
try:
|
# The Parakeet gateway serializes inference on one slot and intermittently
|
||||||
result = await transcribe_audio(
|
# times out when the backend is busy/cold. The recording is already saved,
|
||||||
rec_path,
|
# so retry a couple of times before giving up rather than making the caller
|
||||||
response_format=response_format,
|
# re-run transcribe() by hand.
|
||||||
timestamp_granularities=timestamp_granularities,
|
result = None
|
||||||
diarize=diarize,
|
last_err: TranscriptionError | None = None
|
||||||
num_speakers=num_speakers,
|
for attempt in range(_LISTEN_TRANSCRIBE_ATTEMPTS):
|
||||||
punctuation=punctuation,
|
try:
|
||||||
min_confidence=min_confidence,
|
result = await transcribe_audio(
|
||||||
)
|
rec_path,
|
||||||
except TranscriptionError as e:
|
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 {
|
return {
|
||||||
"error": str(e),
|
"error": (
|
||||||
|
f"Transcription failed after {_LISTEN_TRANSCRIBE_ATTEMPTS} attempts: "
|
||||||
|
f"{last_err}"
|
||||||
|
),
|
||||||
"recorded": str(rec_path),
|
"recorded": str(rec_path),
|
||||||
"saved_to": saved_to,
|
"saved_to": saved_to,
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user