mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-07-30 10:52:15 +00:00
fix(speech): close temp file before transcribing to fix Windows EACCES in faster-whisper backend (#638)
faster-whisper's transcribe() was handed the path of a still-open NamedTemporaryFile; on Windows the open handle is exclusive, so PyAV's reopen failed with EACCES and local STT was broken. Switch to delete=False, close before transcribing, and unlink in a finally. The write is wrapped in the file's context manager so the handle closes even if write() raises, and unlink failures are logged at debug.
This commit is contained in:
@@ -53,6 +53,69 @@ def test_faster_whisper_transcribe():
|
||||
assert result.duration_seconds == 1.5
|
||||
|
||||
|
||||
def test_faster_whisper_transcribe_temp_file_reopenable_and_removed():
|
||||
"""The temp file must be closed before the model reads it, and gone after.
|
||||
|
||||
On Windows, an open NamedTemporaryFile holds an exclusive handle, so
|
||||
PyAV's reopen of the path inside model.transcribe() fails with EACCES
|
||||
unless the file is closed first. Opening the path inside the mocked
|
||||
transcribe reproduces that failure mode on Windows.
|
||||
"""
|
||||
import os
|
||||
|
||||
mock_info = MagicMock()
|
||||
mock_info.language = "en"
|
||||
mock_info.language_probability = 0.95
|
||||
mock_info.duration = 1.5
|
||||
|
||||
seen = {}
|
||||
|
||||
def fake_transcribe(path, **kwargs):
|
||||
seen["path"] = path
|
||||
with open(path, "rb") as fh:
|
||||
seen["content"] = fh.read()
|
||||
return iter(()), mock_info
|
||||
|
||||
mock_model = MagicMock()
|
||||
mock_model.transcribe.side_effect = fake_transcribe
|
||||
|
||||
with patch(
|
||||
"openjarvis.speech.faster_whisper.WhisperModel",
|
||||
return_value=mock_model,
|
||||
):
|
||||
backend = FasterWhisperBackend(model_size="base", device="cpu")
|
||||
backend.transcribe(b"fake audio bytes")
|
||||
|
||||
assert seen["content"] == b"fake audio bytes"
|
||||
assert not os.path.exists(seen["path"])
|
||||
|
||||
|
||||
def test_faster_whisper_transcribe_removes_temp_file_on_error():
|
||||
"""The temp file is cleaned up even when transcription fails."""
|
||||
import os
|
||||
|
||||
seen = {}
|
||||
|
||||
def fake_transcribe(path, **kwargs):
|
||||
seen["path"] = path
|
||||
raise RuntimeError("decode failed")
|
||||
|
||||
mock_model = MagicMock()
|
||||
mock_model.transcribe.side_effect = fake_transcribe
|
||||
|
||||
with patch(
|
||||
"openjarvis.speech.faster_whisper.WhisperModel",
|
||||
return_value=mock_model,
|
||||
):
|
||||
backend = FasterWhisperBackend(model_size="base", device="cpu")
|
||||
with pytest.raises(RuntimeError, match="decode failed"):
|
||||
backend.transcribe(b"fake audio bytes")
|
||||
|
||||
assert "path" in seen
|
||||
assert not os.path.exists(seen["path"])
|
||||
assert "decode failed" in (backend.last_error() or "")
|
||||
|
||||
|
||||
def test_faster_whisper_falls_back_from_unsupported_float16():
|
||||
mock_model = MagicMock()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user