diff options
Diffstat (limited to 'converter/converter.py')
| -rw-r--r-- | converter/converter.py | 44 |
1 files changed, 34 insertions, 10 deletions
diff --git a/converter/converter.py b/converter/converter.py index a58fc33..6d09e09 100644 --- a/converter/converter.py +++ b/converter/converter.py @@ -11,7 +11,8 @@ from datetime import datetime from pathlib import Path from typing import Dict, List, Optional, Tuple -from . import audio, chunking, config, extractors +from . import audio, chunking, config, cover, extractors +from .audio import TrackMeta from .tts import QwenTTSClient logger = logging.getLogger(__name__) @@ -142,31 +143,48 @@ class AudiobookConverter: audio.cleanup_chunks() logger.info("Extracting text...") - sections = extractors.extract_sections(file_path) + book = extractors.extract_book(file_path) + sections = book.sections if not sections or all(not s.text.strip() for s in sections): logger.error("No text extracted") return False stem = output_name or file_path.stem + # Cover art: generated once per book. Named with the chunk_ + # prefix so cleanup_chunks() removes it with the other scratch + # files at the end of the book. + cover_path = cover.generate_cover( + book.title, config.CHUNKS_FOLDER / "chunk_cover.png") + if cover_path: + print(f"[INFO] Generated cover art for '{book.title}'") + meta = TrackMeta(title=book.title, artist=book.author, album=book.title) + # m4b is always a single file; multi-chapter books get embedded # chapter markers so listeners can skip between chapters. if self.output_format == "m4b": if len(sections) > 1: - return self._convert_m4b_with_chapters(sections, stem, start_time) + return self._convert_m4b_with_chapters(sections, stem, start_time, + meta=meta, cover=cover_path) output_path = config.AUDIOBOOKS_FOLDER / f"{stem}.{self.output_format}" - return self._convert_text(sections[0].text, output_path, start_time) + return self._convert_text(sections[0].text, output_path, start_time, + meta=meta, cover=cover_path) if self.single_file or len(sections) == 1: text = "\n\n".join(section.text for section in sections) output_path = config.AUDIOBOOKS_FOLDER / f"{stem}.{self.output_format}" - return self._convert_text(text, output_path, start_time) + return self._convert_text(text, output_path, start_time, + meta=meta, cover=cover_path) success = True for index, section in enumerate(sections, 1): chapter_name = f"{stem}_{index:02d}_{self._sanitize_filename(section.title)}" output_path = config.AUDIOBOOKS_FOLDER / f"{chapter_name}.{self.output_format}" - success = self._convert_text(section.text, output_path, time.time()) and success + track_meta = meta._replace( + title=(section.title or "").strip() or f"Chapter {index}", + track=index, total_tracks=len(sections)) + success = self._convert_text(section.text, output_path, time.time(), + meta=track_meta, cover=cover_path) and success return success except Exception as exc: @@ -177,7 +195,9 @@ class AudiobookConverter: # Always cleanup, even on failure or interrupt audio.cleanup_chunks() - def _convert_m4b_with_chapters(self, sections, stem: str, start_time: float) -> bool: + def _convert_m4b_with_chapters(self, sections, stem: str, start_time: float, + meta: Optional[TrackMeta] = None, + cover: Optional[Path] = None) -> bool: """Convert each chapter to audio, then assemble a single m4b with embedded chapter markers. @@ -208,7 +228,8 @@ class AudiobookConverter: return False output_path = config.AUDIOBOOKS_FOLDER / f"{stem}.{self.output_format}" - if not audio.combine_chapters_to_m4b(chapter_files, titles, output_path, speed=self.speed): + if not audio.combine_chapters_to_m4b(chapter_files, titles, output_path, speed=self.speed, + meta=meta, cover=cover): return False duration = time.time() - start_time logger.info("Conversion completed in %dm %ds: %s", @@ -256,7 +277,9 @@ class AudiobookConverter: def _convert_text(self, text: str, output_path: Path, start_time: float, speed: Optional[float] = None, output_format: Optional[str] = None, - chapter: Optional[Tuple[int, int]] = None) -> bool: + chapter: Optional[Tuple[int, int]] = None, + meta: Optional[TrackMeta] = None, + cover: Optional[Path] = None) -> bool: """Chunk, synthesize, and assemble ``text`` into ``output_path``. When ``chapter`` (a ``(number, total)`` pair) is given, the output is @@ -303,7 +326,8 @@ 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, - intermediate=chapter is not None) + intermediate=chapter is not None, + meta=meta, cover=cover) if success: duration = time.time() - start_time |
