diff options
| author | historia <historiavg@proton.me> | 2026-08-18 02:30:21 -0400 |
|---|---|---|
| committer | historia <historiavg@proton.me> | 2026-08-18 02:30:21 -0400 |
| commit | 6d0e740b4225859457109821a4c77feff72c24b5 (patch) | |
| tree | 3d27c8aa0c9f30140190e226fe2088fd53dc132b /converter | |
| parent | cacc2064aa1e7c81436862db4be6cb8fc836a5cd (diff) | |
| download | tts-audiobook-generator-6d0e740b4225859457109821a4c77feff72c24b5.tar.gz | |
feat: show chapter in console when splitting m4b into chapters
Diffstat (limited to 'converter')
| -rw-r--r-- | converter/audio.py | 17 | ||||
| -rw-r--r-- | converter/converter.py | 39 |
2 files changed, 43 insertions, 13 deletions
diff --git a/converter/audio.py b/converter/audio.py index f1c508c..5a7ec35 100644 --- a/converter/audio.py +++ b/converter/audio.py @@ -210,14 +210,17 @@ def _collect_chunk_files(total_chunks: int, def combine_chunks(total_chunks: int, output_path: Path, chunk_results: Optional[Dict[int, Optional[Path]]] = None, - speed: float = 1.0, output_format: str = "mp3") -> bool: + speed: float = 1.0, output_format: str = "mp3", + intermediate: bool = False) -> bool: """Combine audio chunks into the final audiobook using ffmpeg's concat demuxer. ``chunk_results`` maps chunk numbers to the audio file each chunk produced (None for failed chunks); failed and missing chunks are skipped. When ``speed`` differs from 1.0, an additional speed-adjusted copy is written next to the normal-speed file. Chunks are streamed by ffmpeg, so the whole - book is never held in memory. + book is never held in memory. Set ``intermediate`` for scratch chapter + audio on the way to a larger output (e.g. a chaptered m4b) so save + messages don't present it as the final audiobook. """ if shutil.which("ffmpeg") is None or shutil.which("ffprobe") is None: logger.error("ffmpeg and ffprobe are required to combine audio chunks (install ffmpeg)") @@ -265,8 +268,14 @@ def combine_chunks(total_chunks: int, output_path: Path, if not duration_ok: return False - logger.info("Audiobook saved: %s (%d/%d chunks)", output_path, len(chunk_files), total_chunks) - print(f"[INFO] Saved audiobook: {output_path.name} ({len(chunk_files)}/{total_chunks} chunks)") + if intermediate: + logger.info("Chapter audio saved (intermediate): %s (%d/%d chunks)", + output_path, len(chunk_files), total_chunks) + print(f"[INFO] Saved chapter audio (intermediate): {output_path.name} " + f"({len(chunk_files)}/{total_chunks} chunks)") + else: + logger.info("Audiobook saved: %s (%d/%d chunks)", output_path, len(chunk_files), total_chunks) + print(f"[INFO] Saved audiobook: {output_path.name} ({len(chunk_files)}/{total_chunks} chunks)") if speed_path is not None: logger.info("Saved speed-adjusted audiobook (%gx): %s", speed, speed_path) diff --git a/converter/converter.py b/converter/converter.py index d5a1096..bc64f38 100644 --- a/converter/converter.py +++ b/converter/converter.py @@ -8,7 +8,7 @@ import traceback from collections import Counter from datetime import datetime from pathlib import Path -from typing import Dict, List, Optional +from typing import Dict, List, Optional, Tuple from . import audio, chunking, config, extractors from .tts import QwenTTSClient @@ -143,15 +143,22 @@ class AudiobookConverter: """ chapter_files = [] titles = [] + total_chapters = len(sections) for index, section in enumerate(sections, 1): chapter_path = config.CHUNKS_FOLDER / f"chapter_{index:04d}.wav" + title = (section.title or "").strip() or f"Chapter {index}" + print(f"\n{'=' * 50}") + print(f"CHAPTER {index}/{total_chapters}: {title}") + print(f"{'=' * 50}") + logger.info("Converting chapter %d/%d: %s", index, total_chapters, title) if not self._convert_text(section.text, chapter_path, time.time(), - speed=1.0, output_format="wav"): + speed=1.0, output_format="wav", + chapter=(index, total_chapters)): logger.warning("Skipping chapter %d (%s) due to conversion failure", - index, section.title) + index, title) continue chapter_files.append(chapter_path) - titles.append(section.title or f"Chapter {index}") + titles.append(title) if not chapter_files: logger.error("No chapters were successfully converted") @@ -205,8 +212,14 @@ class AudiobookConverter: def _convert_text(self, text: str, output_path: Path, start_time: float, speed: Optional[float] = None, - output_format: Optional[str] = None) -> bool: - """Chunk, synthesize, and assemble ``text`` into ``output_path``.""" + output_format: Optional[str] = None, + chapter: Optional[Tuple[int, int]] = None) -> bool: + """Chunk, synthesize, and assemble ``text`` into ``output_path``. + + When ``chapter`` (a ``(number, total)`` pair) is given, the output is + an intermediate per-chapter file and progress messages are phrased + accordingly instead of implying the whole book is done. + """ if speed is None: speed = self.speed if output_format is None: @@ -246,14 +259,22 @@ class AudiobookConverter: # Combine chunks (only the successful ones) success = audio.combine_chunks(total_chunks, output_path, chunk_results=results, - speed=speed, output_format=output_format) + speed=speed, output_format=output_format, + intermediate=chapter is not None) if success: duration = time.time() - start_time minutes = int(duration // 60) seconds = int(duration % 60) - logger.info("Conversion completed in %dm %ds: %s", minutes, seconds, output_path) - print(f"[SUCCESS] Conversion completed in {minutes}m {seconds}s") + if chapter is not None: + logger.info("Chapter %d/%d converted in %dm %ds (%d/%d chunks)", + chapter[0], chapter[1], minutes, seconds, + successful_chunks, total_chunks) + print(f"[INFO] Chapter {chapter[0]}/{chapter[1]} converted " + f"({successful_chunks}/{total_chunks} chunks)") + else: + logger.info("Conversion completed in %dm %ds: %s", minutes, seconds, output_path) + print(f"[SUCCESS] Conversion completed in {minutes}m {seconds}s") else: logger.error("Failed to combine chunks into final audiobook") |
