From 5611f709367677b77a482aa8e396fd3bc6a0ab26 Mon Sep 17 00:00:00 2001 From: historia Date: Thu, 27 Aug 2026 19:00:44 -0400 Subject: feat: flags for converting individual book files --input-book --output-book --- app/converter/converter.py | 54 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 16 deletions(-) (limited to 'app/converter') diff --git a/app/converter/converter.py b/app/converter/converter.py index d2d06b9..e1649c2 100644 --- a/app/converter/converter.py +++ b/app/converter/converter.py @@ -788,6 +788,8 @@ class AudiobookConverter: output_format: str, instructions: Optional[str] = None, confirm: Optional[Callable[[str, bool], bool]] = None, + book_files: Optional[List[Path]] = None, + output_name: Optional[str] = None, ) -> Tuple[List[Path], List[Tuple[Path, str]]]: """Discover books and ask every overwrite question up front. @@ -801,35 +803,55 @@ class AudiobookConverter: nothing to convert) never waits on a slow server handshake. CONFIRM replaces the console ``input()`` prompt (the hub passes a TUI yes/no dialog). + + BOOK_FILES overrides the books-folder scan with an explicit list + (a single --input-file book; still filtered to supported formats), + and OUTPUT_NAME overrides the computed output name with a verbatim + base name (--output-file's stem, no narrator tag or stem-collision + suffix). Both default to the directory-scan behavior. """ - book_files = sorted( - f for f in BOOKS_FOLDER.iterdir() - if f.is_file() and f.suffix.lower() in SUPPORTED_FORMATS - ) + if book_files is None: + book_files = sorted( + f for f in BOOKS_FOLDER.iterdir() + if f.is_file() and f.suffix.lower() in SUPPORTED_FORMATS + ) + else: + book_files = sorted( + f for f in book_files + if f.is_file() and f.suffix.lower() in SUPPORTED_FORMATS + ) if not book_files: return [], [] print(f"[INFO] Found {len(book_files)} books to convert") - # Avoid output collisions when two books share a stem (e.g. dune.txt + dune.epub). - stem_counts: Dict[str, int] = Counter(book_file.stem for book_file in book_files) + # Compute the output name each book would produce. An explicit + # name (--output-file) is used verbatim for the single book; + # otherwise names carry the narrator tag and a stem-collision + # suffix when two books share a stem (e.g. dune.txt + dune.epub). + if output_name is not None: + names = [(book_files[0], output_name)] + else: + stem_counts: Dict[str, int] = Counter(book_file.stem for book_file in book_files) + narrator_tag = AudiobookConverter.compute_narrator_tag( + backend, voice, voice_mode, voice_clone_ref_audio, instructions) + names = [] + for book_file in book_files: + name = book_file.stem + if stem_counts[book_file.stem] > 1: + name = f"{book_file.stem}_{book_file.suffix.lstrip('.')}" + names.append((book_file, f"{name}_{narrator_tag}")) # Ask every overwrite question up front, before any conversion # starts, so the rest of the run is unattended. planned: List[Tuple[Path, str]] = [] - narrator_tag = AudiobookConverter.compute_narrator_tag( - backend, voice, voice_mode, voice_clone_ref_audio, instructions) - for book_file in book_files: - output_name = book_file.stem - if stem_counts[book_file.stem] > 1: - output_name = f"{book_file.stem}_{book_file.suffix.lstrip('.')}" - output_name = f"{output_name}_{narrator_tag}" - existing = find_existing_outputs(output_name, output_format) - if existing and not prompt_overwrite(existing, output_name, + for book_file, name in names: + existing = find_existing_outputs(name, output_format) + if existing and not prompt_overwrite(existing, name, confirm=confirm): print(f"[INFO] Skipping {book_file.name} (existing output kept)") continue - planned.append((book_file, output_name)) + planned.append((book_file, name)) return book_files, planned # ------------------------------------------------------------------ -- cgit v1.2.3