diff options
| author | historia <historiavg@proton.me> | 2026-08-21 00:15:57 -0400 |
|---|---|---|
| committer | historia <historiavg@proton.me> | 2026-08-21 00:15:57 -0400 |
| commit | fea9222740da007f1d7befcd7dee035265c0e5d1 (patch) | |
| tree | 8ae6a1305940ec94f6f495aa59c5cdc95cd803dd | |
| parent | 0017f6b0421e549e9a2cdfadc104be64433724d3 (diff) | |
| download | tts-audiobook-generator-fea9222740da007f1d7befcd7dee035265c0e5d1.tar.gz | |
feat: exit script if any chunk fails
| -rw-r--r-- | converter/converter.py | 36 | ||||
| -rw-r--r-- | tests/test_converter.py | 24 |
2 files changed, 46 insertions, 14 deletions
diff --git a/converter/converter.py b/converter/converter.py index 0ae1506..c5f1788 100644 --- a/converter/converter.py +++ b/converter/converter.py @@ -381,9 +381,9 @@ class AudiobookConverter: speed=1.0, output_format="wav", chapter=(index, total_chapters), debug_dir=self._chapter_debug_dir(debug_dir, index, title)): - logger.warning("Skipping chapter %d (%s) due to conversion failure", - index, title) - continue + logger.error("Chapter %d (%s) failed; aborting the conversion", + index, title) + return False chapter_files.append(chapter_path) titles.append(title) @@ -405,9 +405,11 @@ class AudiobookConverter: """Synthesize chunks sequentially, preserving order and naming. Returns a mapping of chunk number to the generated audio path, with - None for chunks that failed after retries. When ``debug_dir`` is - given (--debug), each chunk's request text and returned audio are - also dumped there, and every request/response is logged. + None for chunks that failed. Generation stops at the first failed + chunk: a partial audiobook is never assembled, so the remaining + chunks are not requested. When ``debug_dir`` is given (--debug), + each chunk's request text and returned audio are also dumped there, + and every request/response is logged. """ total_chunks = len(chunks) if self.client_chunks: @@ -438,11 +440,15 @@ class AudiobookConverter: print(f"[OK] Chunk {chunk_num:3d}/{total_chunks} completed") logger.info("+ Chunk %d/%d completed", chunk_num, total_chunks) else: - logger.error("Chunk %d/%d failed", chunk_num, total_chunks) + logger.error("Chunk %d/%d failed; aborting the remaining chunks", + chunk_num, total_chunks) + break except Exception as exc: results[chunk_num] = None - logger.error("Chunk %d/%d error: %s", chunk_num, total_chunks, exc) + logger.error("Chunk %d/%d error: %s; aborting the remaining chunks", + chunk_num, total_chunks, exc) + break successful_chunks = sum(1 for path in results.values() if path) if self.client_chunks: @@ -528,13 +534,11 @@ class AudiobookConverter: results = self._synthesize_chunks(chunks, debug_dir=debug_dir) successful_chunks = sum(1 for path in results.values() if path) - if successful_chunks == 0: - logger.error("No chunks were successfully processed") - return False - if successful_chunks < total_chunks: - logger.warning("Only %d/%d chunks succeeded. Proceeding with partial audiobook.", - successful_chunks, total_chunks) + logger.error("Chunk processing incomplete (%d/%d chunks); " + "aborting without producing an audiobook", + successful_chunks, total_chunks) + return False success = audio.combine_chunks(total_chunks, output_path, chunk_results=results, speed=speed, output_format=output_format, @@ -710,6 +714,10 @@ class AudiobookConverter: except Exception as exc: logger.error("Unexpected error: %s", exc) results[book_file.name] = False + if not results[book_file.name]: + logger.error("Conversion of %s failed; aborting the remaining books", + book_file.name) + break successful = sum(results.values()) total = len(results) diff --git a/tests/test_converter.py b/tests/test_converter.py index d525c18..e7c0776 100644 --- a/tests/test_converter.py +++ b/tests/test_converter.py @@ -258,6 +258,20 @@ class DebugDumpTests(unittest.TestCase): results = self.converter._synthesize_chunks(["Hello."], debug_dir=blocker / "book") self.assertEqual(results, {1: audio}) + def test_failed_chunk_stops_remaining_chunks(self): + audio = self._chunk_source("chunk_0001.wav") + self.converter.tts.process_chunk_with_retry.side_effect = [audio, None, audio] + results = self.converter._synthesize_chunks(["One.", "Two.", "Three."]) + self.assertEqual(results, {1: audio, 2: None}) + self.assertEqual(self.converter.tts.process_chunk_with_retry.call_count, 2) + + def test_raising_chunk_stops_remaining_chunks(self): + audio = self._chunk_source("chunk_0001.wav") + self.converter.tts.process_chunk_with_retry.side_effect = [audio, RuntimeError("boom")] + results = self.converter._synthesize_chunks(["One.", "Two.", "Three."]) + self.assertEqual(results, {1: audio, 2: None}) + self.assertEqual(self.converter.tts.process_chunk_with_retry.call_count, 2) + def test_debug_flag_wiring(self): with patch("converter.converter.QwenTTSClient"): self.assertFalse(AudiobookConverter().debug) @@ -411,6 +425,16 @@ class ServerSideChunkingOutputTests(unittest.TestCase): self.assertNotIn("single request", out) self.assertIn("Chapter 2/5 converted (1/1 chunks)", out) + def test_partial_chunks_abort_without_assembling(self): + converter = self._converter(client_chunks=True) + converter.tts.process_chunk_with_retry.side_effect = ["chunk_0001.wav", None] + text = " ".join(f"word{i}" for i in range(8)) + with patch.object(config, "CHUNK_SIZE", 5), \ + patch.object(converter_mod.audio, "combine_chunks") as mock_combine: + ok = converter._convert_text(text, Path("out.mp3"), time.time()) + self.assertFalse(ok) + mock_combine.assert_not_called() + class PromptOverwriteTests(unittest.TestCase): def test_single_file_yes(self): |
