aboutsummaryrefslogtreecommitdiff
path: root/app/tests/test_backends_audiocpp.py
diff options
context:
space:
mode:
Diffstat (limited to 'app/tests/test_backends_audiocpp.py')
-rw-r--r--app/tests/test_backends_audiocpp.py163
1 files changed, 152 insertions, 11 deletions
diff --git a/app/tests/test_backends_audiocpp.py b/app/tests/test_backends_audiocpp.py
index 3e97b82..6f97438 100644
--- a/app/tests/test_backends_audiocpp.py
+++ b/app/tests/test_backends_audiocpp.py
@@ -1,5 +1,6 @@
"""Tests for the audio.cpp backend setup module (backends/audiocpp.py)."""
+import argparse
import io
import json
import sys
@@ -983,9 +984,10 @@ class TranscribeWavDirTests(unittest.TestCase):
self._td.cleanup()
def test_transcribes_to_stem_map_with_absolute_paths(self):
- transcripts = {str(self.narrator): "First.",
- str(self.other): "Second."}
- with patch.object(make_server.voices, "transcribe_reference_audio",
+ transcripts = {str(self.narrator): ("First.", "ok"),
+ str(self.other): ("Second.", "ok")}
+ with patch.object(make_server.voices,
+ "transcribe_reference_audio_detailed",
side_effect=lambda path, model_name="base":
transcripts[path]):
result = make_server.voices.transcribe_wav_dir(
@@ -994,14 +996,27 @@ class TranscribeWavDirTests(unittest.TestCase):
self.assertEqual(result["narrator"], "First.")
def test_failed_transcription_keeps_empty_string(self):
- with patch.object(make_server.voices, "transcribe_reference_audio",
- return_value=None):
+ with patch.object(make_server.voices,
+ "transcribe_reference_audio_detailed",
+ return_value=(None, "no speech detected")):
result = make_server.voices.transcribe_wav_dir([self.narrator], "base")
self.assertEqual(result["narrator"], "")
+ def test_failed_transcription_prints_the_reason(self):
+ buffer = io.StringIO()
+ with patch.object(make_server.voices,
+ "transcribe_reference_audio_detailed",
+ return_value=(None, "faster_whisper heard no speech")), \
+ redirect_stdout(buffer):
+ make_server.voices.transcribe_wav_dir([self.narrator], "base")
+ output = buffer.getvalue()
+ self.assertIn("No transcript for 'narrator'", output)
+ self.assertIn("faster_whisper heard no speech", output)
+
def test_whisper_model_name_passed_through(self):
- with patch.object(make_server.voices, "transcribe_reference_audio",
- return_value="text") as mock_transcribe:
+ with patch.object(make_server.voices,
+ "transcribe_reference_audio_detailed",
+ return_value=("text", "ok")) as mock_transcribe:
make_server.voices.transcribe_wav_dir([self.narrator], "large-v3")
self.assertEqual(mock_transcribe.call_args.kwargs["model_name"],
"large-v3")
@@ -1015,6 +1030,122 @@ class TranscribeWavDirTests(unittest.TestCase):
self.assertIn("other|World.", text)
+class TranscribePlanTests(unittest.TestCase):
+ """_transcribe: plan application and transcript wipe protection."""
+
+ def setUp(self):
+ self._td = tempfile.TemporaryDirectory()
+ self.folder = Path(self._td.name)
+ self.narrator = self.folder / "narrator.wav"
+ self.narrator.write_bytes(b"x")
+ self.args = argparse.Namespace(input_dir=self.folder,
+ whisper_model="base")
+
+ def tearDown(self):
+ self._td.cleanup()
+
+ def test_all_mode_retranscribes_everything(self):
+ with patch.object(make_server.voices,
+ "transcribe_reference_audio_detailed",
+ return_value=("New words.", "ok")):
+ transcripts, write = make_server.voices._transcribe(
+ self.args, {"mode": "all", "missing": [], "existing": {}})
+ self.assertTrue(write)
+ self.assertEqual(transcripts, {"narrator": "New words."})
+
+ def test_empty_retranscription_keeps_existing_transcript(self):
+ # A failed re-transcription must never overwrite known-good text
+ # with a blank: a blank prompt_text entry makes the server reject
+ # every clone request for that voice.
+ with patch.object(make_server.voices,
+ "transcribe_reference_audio_detailed",
+ return_value=(None, "backend broken")), \
+ redirect_stdout(io.StringIO()) as buffer:
+ transcripts, write = make_server.voices._transcribe(
+ self.args, {"mode": "all", "missing": [],
+ "existing": {"narrator": "Good words."}})
+ self.assertTrue(write)
+ self.assertEqual(transcripts, {"narrator": "Good words."})
+ self.assertIn("Kept the existing transcript for 'narrator'",
+ buffer.getvalue())
+
+ def test_missing_mode_merges_new_with_existing(self):
+ with patch.object(make_server.voices,
+ "transcribe_reference_audio_detailed",
+ return_value=("Fresh text.", "ok")):
+ transcripts, _write = make_server.voices._transcribe(
+ self.args, {"mode": "missing", "missing": [self.narrator],
+ "existing": {}})
+ self.assertEqual(transcripts, {"narrator": "Fresh text."})
+
+ def test_unusable_backend_warns_with_the_reason(self):
+ buffer = io.StringIO()
+ with patch.object(make_server.voices, "whisper_backend_problem",
+ return_value="faster_whisper is installed but "
+ "failed to import: boom"), \
+ patch.object(make_server.voices,
+ "transcribe_reference_audio_detailed",
+ return_value=("text", "ok")), \
+ redirect_stdout(buffer):
+ make_server.voices._transcribe(
+ self.args, {"mode": "all", "missing": [], "existing": {}})
+ output = buffer.getvalue()
+ self.assertIn("No usable Whisper backend", output)
+ self.assertIn("failed to import: boom", output)
+
+
+class WizardTranscribeStepTests(unittest.TestCase):
+ """The setup lane's transcribe step: rc reflects unusable transcripts."""
+
+ def setUp(self):
+ self._td = tempfile.TemporaryDirectory()
+ self.folder = Path(self._td.name)
+ (self.folder / "narrator.wav").write_bytes(b"x")
+ self.args = argparse.Namespace(input_dir=None, whisper_model="base")
+ self.settings = {
+ "audiocpp_dir": self.folder,
+ "wav_dir": self.folder,
+ "include_clone": True,
+ "plan": {"mode": "all", "missing": [], "existing": {}},
+ "build": None,
+ "model_entries": [],
+ }
+
+ def tearDown(self):
+ self._td.cleanup()
+
+ def _transcribe_step(self):
+ lanes = make_server.wizard._execute_lanes(self.settings, self.args)
+ return lanes[0].steps[0]
+
+ def test_blank_transcripts_fail_the_step(self):
+ step = self._transcribe_step()
+ with patch.object(make_server.voices,
+ "transcribe_reference_audio_detailed",
+ return_value=(None, "broken backend")), \
+ redirect_stdout(io.StringIO()) as buffer:
+ rc = step.work(None, None)
+ self.assertEqual(rc, 1)
+ self.assertIn("No transcript for: narrator", buffer.getvalue())
+
+ def test_good_transcripts_pass_the_step(self):
+ step = self._transcribe_step()
+ with patch.object(make_server.voices,
+ "transcribe_reference_audio_detailed",
+ return_value=("Words.", "ok")), \
+ redirect_stdout(io.StringIO()):
+ self.assertEqual(step.work(None, None), 0)
+
+ def test_no_clone_families_passes_without_transcribing(self):
+ self.settings["include_clone"] = False
+ self.settings["plan"] = None
+ step = self._transcribe_step()
+ with patch.object(make_server.voices,
+ "transcribe_reference_audio_detailed") as mock_transcribe:
+ self.assertEqual(step.work(None, None), 0)
+ mock_transcribe.assert_not_called()
+
+
class DesignPackageTests(unittest.TestCase):
"""Voice-design package detection."""
@@ -1594,14 +1725,24 @@ class NonInteractiveMainTests(unittest.TestCase):
argv = ["backends/audiocpp.py"] + argv
transcribe_effect = transcribe if transcribe is not None \
else MagicMock()
+
+ def detailed(path, model_name="base"):
+ result = transcribe_effect(path, model_name=model_name)
+ if isinstance(result, tuple):
+ return result
+ return (result, "ok" if result
+ else "faster_whisper is not installed (test stub)")
+
with patch.object(sys, "argv", argv), \
patch.object(make_server.build, "find_local_checkout",
return_value=None if no_checkout
else self.checkout), \
- patch.object(make_server.voices, "transcribe_reference_audio",
- side_effect=transcribe_effect), \
- patch.object(make_server.voices, "whisper_backend_available",
- return_value=whisper):
+ patch.object(make_server.voices,
+ "transcribe_reference_audio_detailed",
+ side_effect=detailed), \
+ patch.object(make_server.voices, "whisper_backend_problem",
+ return_value=None if whisper else
+ "faster_whisper is not installed"):
return make_server.wizard.main()
def _args(self, *extra):