diff --git a/docs/environment-variables.md b/docs/environment-variables.md index d77ba456..5db66394 100644 --- a/docs/environment-variables.md +++ b/docs/environment-variables.md @@ -247,7 +247,7 @@ Seconds since the last WireGuard handshake before the healthcheck bounces the tu | `CALIBRE_WEB_URL` | Adds a navigation button to your book library (Calibre-Web Automated, Grimmory, etc). | string | _none_ | | `AUDIOBOOK_LIBRARY_URL` | Adds a separate navigation button for your audiobook library (Audiobookshelf, Plex, etc). When both URLs are set, icons are shown instead of text. | string | _none_ | | `SUPPORTED_FORMATS` | Book formats to include in search results. ZIP/RAR archives are extracted automatically and book files are used if found. | string (comma-separated) | `epub,mobi,azw3,fb2,djvu,cbz,cbr` | -| `SUPPORTED_AUDIOBOOK_FORMATS` | Audiobook formats to include in search results. ZIP/RAR archives are extracted automatically and audiobook files are used if found. | string (comma-separated) | `m4b,mp3,m4a,flac,ogg,wma,aac,wav,opus,zip,rar` | +| `SUPPORTED_AUDIOBOOK_FORMATS` | Audiobook formats to include in search results. ZIP/RAR archives are extracted automatically and audiobook files are used if found. | string (comma-separated) | `m4b,mp3,m4a,mp4,flac,ogg,wma,aac,wav,opus,zip,rar` | | `BOOK_LANGUAGE` | Default language filter for searches. | string (comma-separated) | `en` |
@@ -296,7 +296,7 @@ Book formats to include in search results. ZIP/RAR archives are extracted automa Audiobook formats to include in search results. ZIP/RAR archives are extracted automatically and audiobook files are used if found. - **Type:** string (comma-separated) -- **Default:** `m4b,mp3,m4a,flac,ogg,wma,aac,wav,opus,zip,rar` +- **Default:** `m4b,mp3,m4a,mp4,flac,ogg,wma,aac,wav,opus,zip,rar` #### `BOOK_LANGUAGE` diff --git a/shelfmark/core/utils.py b/shelfmark/core/utils.py index 3dce7518..417544cc 100644 --- a/shelfmark/core/utils.py +++ b/shelfmark/core/utils.py @@ -122,7 +122,12 @@ def is_audiobook(content_type: str | None) -> bool: # had drifted apart: the settings UI only offered m4b/mp3/m4a, which meant a FLAC # audiobook could never be enabled, was silently dropped from every search result, and # was rejected after download as "format not supported". -AUDIOBOOK_FORMATS = ("m4b", "mp3", "m4a", "flac", "ogg", "wma", "aac", "wav", "opus") +# +# "mp4" is here because some trackers (MyAnonamouse in particular) ship AAC audiobooks +# as per-chapter .mp4 files - the same ISO-BMFF container as .m4a/.m4b, just with the +# generic extension. Without it those releases downloaded fine and then failed +# post-processing with "No book files found in download". +AUDIOBOOK_FORMATS = ("m4b", "mp3", "m4a", "mp4", "flac", "ogg", "wma", "aac", "wav", "opus") # Multi-file audiobooks are almost always distributed as an archive. These are containers # rather than formats: they are what a *release* looks like, and the formats above are diff --git a/shelfmark/download/clients/alldebrid.py b/shelfmark/download/clients/alldebrid.py index 193f98fc..2babb600 100644 --- a/shelfmark/download/clients/alldebrid.py +++ b/shelfmark/download/clients/alldebrid.py @@ -76,6 +76,7 @@ _BOOK_EXTENSIONS = ( ".m4b", ".mobi", ".mp3", + ".mp4", ".ogg", ".opus", ".pdf", diff --git a/shelfmark/download/clients/realdebrid.py b/shelfmark/download/clients/realdebrid.py index 1ceab58a..8c155ea7 100644 --- a/shelfmark/download/clients/realdebrid.py +++ b/shelfmark/download/clients/realdebrid.py @@ -80,6 +80,7 @@ _BOOK_EXTENSIONS = ( ".m4b", ".mobi", ".mp3", + ".mp4", ".ogg", ".opus", ".pdf", diff --git a/shelfmark/release_sources/irc/source.py b/shelfmark/release_sources/irc/source.py index 6ee134b5..c5057eba 100644 --- a/shelfmark/release_sources/irc/source.py +++ b/shelfmark/release_sources/irc/source.py @@ -428,14 +428,15 @@ class IRCReleaseSource(ReleaseSource): "m4b": 0, "mp3": 1, "m4a": 2, - "flac": 3, - "opus": 4, - "ogg": 5, - "aac": 6, - "wav": 7, - "wma": 8, - "rar": 9, - "zip": 10, + "mp4": 3, + "flac": 4, + "opus": 5, + "ogg": 6, + "aac": 7, + "wav": 8, + "wma": 9, + "rar": 10, + "zip": 11, } def _convert_to_releases( diff --git a/tests/core/test_processing_integration.py b/tests/core/test_processing_integration.py index a247db88..0aeb4156 100644 --- a/tests/core/test_processing_integration.py +++ b/tests/core/test_processing_integration.py @@ -1458,3 +1458,47 @@ def test_external_directory_prefers_files_over_archives_and_keeps_source( # TMP staging should be cleaned. assert list(staging.iterdir()) == [] + + +def test_audiobook_multifile_mp4_chapters_are_book_files(tmp_path): + """Per-chapter .mp4 audiobooks (as MyAnonamouse ships AAC releases) must be + recognised as book files instead of failing with "No book files found".""" + from shelfmark.download.postprocess.router import ( + post_process_download as _post_process_download, + ) + + source_dir = tmp_path / "downloads" / "Andy Weir (2020) The Martian" + source_dir.mkdir(parents=True) + for part in (1, 2): + (source_dir / f"{part:04d} Andy Weir (2020) The Martian.mp4").write_text(f"audio{part}") + (source_dir / "cover.jpg").write_text("jpg") + + ingest = tmp_path / "ingest" + ingest.mkdir() + task = DownloadTask( + task_id="mp4-audio-grouped", + source="prowlarr", + title="The Martian", + author="Andy Weir", + format="mp4", + content_type="audiobook", + search_mode=SearchMode.UNIVERSAL, + ) + + with patch("shelfmark.core.config.config") as mock_config: + mock_config.get = _build_config( + ingest, + organization="rename_and_group", + supported_audiobook_formats=["mp4"], + ) + mock_config.CUSTOM_SCRIPT = None + + result = _post_process_download(source_dir, task, Event(), lambda *_args: None) + + grouped_dir = ingest / "Andy Weir (2020) The Martian" + assert result is not None + assert Path(result).parent == grouped_dir + assert sorted(path.name for path in grouped_dir.glob("*.mp4")) == [ + "0001 Andy Weir (2020) The Martian.mp4", + "0002 Andy Weir (2020) The Martian.mp4", + ]