From ff685112a67d4e5e70d379ce8bbc788e20517115 Mon Sep 17 00:00:00 2001 From: Richie Cahill Date: Wed, 10 Jun 2026 16:33:16 -0400 Subject: [PATCH] fixed omnibus for audio books --- python/tools/audiobook/audible_convert.py | 18 +++++++++++++++++- python/tools/audiobook/metadata_agent.py | 6 ++++++ tests/test_audible_convert.py | 19 ++++++++++++++++++- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/python/tools/audiobook/audible_convert.py b/python/tools/audiobook/audible_convert.py index 0c77346..4d38c3a 100644 --- a/python/tools/audiobook/audible_convert.py +++ b/python/tools/audiobook/audible_convert.py @@ -44,6 +44,7 @@ class ConversionConfig: dry_run: bool overwrite: bool work_directory_name: str = ".audible_convert" + dry_run_directory_name: str = "dry-run" temp_directory_name: str = "tmp" log_directory_name: str = "logs" review_directory_name: str = "review" @@ -73,7 +74,10 @@ def main( input_directory: Annotated[Path, typer.Argument(help="Directory audible-cli downloads AAX files into.")], output_directory: Annotated[Path, typer.Argument(help="Audiobook output directory.")], *, - dry_run: Annotated[bool, typer.Option("--dry-run", help="Print planned output files without converting.")] = False, + dry_run: Annotated[ + bool, + typer.Option("--dry-run", help="Print planned output files and write marker files without converting."), + ] = False, overwrite: Annotated[bool, typer.Option("--overwrite", help="Overwrite existing M4B files.")] = False, ) -> None: """Convert AAX files from a download directory into M4B files.""" @@ -271,6 +275,18 @@ def dry_run_aax_file_with_agent( ) typer.echo(f"{aax_file} -> REVIEW {review_file}") else: + stem = output_stem(metadata) + dry_run_file = ( + config.resolved_output / config.work_directory_name / config.dry_run_directory_name / stem / f"{stem}.m4b" + ) + dry_run_file.parent.mkdir(parents=True, exist_ok=True) + dry_run_file.write_text(f"{destination}\n", encoding="utf-8") + write_agent_log( + log_file, + "dry_run_file_written", + destination=str(destination), + path=str(dry_run_file), + ) typer.echo(f"{aax_file} -> {destination}") diff --git a/python/tools/audiobook/metadata_agent.py b/python/tools/audiobook/metadata_agent.py index e269d59..1828c79 100644 --- a/python/tools/audiobook/metadata_agent.py +++ b/python/tools/audiobook/metadata_agent.py @@ -468,6 +468,12 @@ Rules: - title must be a canonical title slug using lower-case words separated by hyphens. - Use series_id null and series_index 0 for standalone books. - If you use a series_id, series_index must be an integer greater than or equal to 1. +- Detect omnibus or box-set editions that contain multiple numbered novels, books, or novellas. +- For an omnibus, make a best-effort range from the filename, tags, and catalog rows. Keep series_index as the + first covered book number and include the range in the title when the source title includes it, for example + books-1-3. +- Be careful with omnibuses of novels or novellas later published as one book: keep the omnibus as the audiobook's + book record unless catalog rows clearly identify a better match. - Do not create publisher collections or author collections as series unless the book metadata clearly gives a numbered series. - Series belong to authors. Use a series_id only when it belongs to the selected author_id. diff --git a/tests/test_audible_convert.py b/tests/test_audible_convert.py index 3242e6c..22cdbe3 100644 --- a/tests/test_audible_convert.py +++ b/tests/test_audible_convert.py @@ -190,6 +190,14 @@ def test_write_agent_log_serializes_metadata_as_json_object(tmp_path) -> None: assert record["path"] == str(tmp_path) +def test_system_prompt_instructs_agent_to_detect_omnibuses() -> None: + prompt = metadata_agent.system_prompt() + + assert "Detect omnibus or box-set editions" in prompt + assert "books-1-3" in prompt + assert "Keep series_index as the" in prompt + + def test_standard_book_metadata_accepts_valid_tool_output(tmp_path, monkeypatch, audiobook_engine) -> None: install_fake_ollama( monkeypatch, @@ -928,7 +936,16 @@ def test_main_dry_run_prints_outputs_without_converting(tmp_path, monkeypatch, c assert calls == [] assert capsys.readouterr().out == ( - f"{source} -> " + f"{source} -> {output_directory / 'charles_lamb-alice_01-alice' / 'charles_lamb-alice_01-alice.m4b'}\n" + ) + dry_run_file = ( + output_directory + / ".audible_convert" + / "dry-run" + / "charles_lamb-alice_01-alice" + / "charles_lamb-alice_01-alice.m4b" + ) + assert dry_run_file.read_text(encoding="utf-8") == ( f"{output_directory / 'charles_lamb-alice_01-alice' / 'charles_lamb-alice_01-alice.m4b'}\n" ) assert (output_directory / ".audible_convert" / "logs").is_dir()