mirror of
https://github.com/calibrain/shelfmark.git
synced 2026-10-04 08:51:13 +01:00
## Human-Written explanation:
Following up on previous PR to use MAM ID to get nararrator and series
to show up in search, this PR will allow the series and nararrator
fields to be added to the path when saving an audiobook.
I have tested my ghcr.io image on my instance and it seemed to work
properly.
All the text below is written by Claude.
# feat(naming): {Narrator} placeholder and MyAnonamouse series fallback
Follow-up to #1390 and #1399. Related to #605 and #934 (narrator
support).
## Problem
Several narrations of the same audiobook currently land in the same
folder, because nothing in the path template tells them apart. Keeping
more than one version means renaming folders by hand after every
download.
Since #1390, MyAnonamouse results carry the release's narrator and
series in `extra`, but the naming templates can't use them: there is no
`{Narrator}` placeholder, and `{Series}` / `{SeriesPosition}` only come
from the metadata provider.
## Changes
- **`{Narrator}` placeholder** (`core/naming.py`,
`download/postprocess/transfer.py`, `core/models.py`): `DownloadTask`
gains `narrator`, read from `narrator` or `extra.narrator` when a
release is queued, and kept in the restart-safe retry payload. It's
available in audiobook templates like any other placeholder, including
prefix/suffix blocks such as `{ - Narrator}`.
- **Audiobookshelf folder style**: Audiobookshelf reads the narrator
from folder names like `Title {Narrator}`. The template `{Title}
{{Narrator}}` already matches as `{` + `{Narrator}`; the parser now also
drops the closing brace when the narrator is empty, so the folder is
`Title` rather than `Title }`.
- **No stray spaces around `/`**: an empty placeholder at the start or
end of a folder name no longer leaves a space there (e.g. `Title
/Title`). This applies to all templates.
- **Series fallback** (`release_sources/prowlarr/mam.py`, `source.py`,
`download/orchestrator.py`): MAM enrichment also stores the first
series' name and number as `extra.series_name` /
`extra.series_position`, which queueing already falls back to when the
metadata provider has no series. The provider's series still wins. A
release-level series number is only used when it names the same series
as the provider, so a provider series is never paired with the number of
a different MAM series (an omnibus, for example).
- **Settings and preview** (`config/settings.py`,
`namingTemplatePreview.ts`): the audiobook template descriptions list
`{Narrator}` and explain `{{Narrator}}`. The settings page preview
mirrors the parser changes and offers `{Narrator}` under "Insert
variable" for audiobook templates only.
- **Docs**: regenerated `environment-variables.md`. This also picked up
the Library Check settings, which weren't in the generated docs yet;
happy to drop that part to keep the diff focused.
## Example
Audiobook Path Template `{Author}/{Title} {{Narrator}}/{Title}`:
| Narrator | Result |
|----------|--------|
| Samuel Roukin | `Christopher Ruocchio/Empire of Silence {Samuel
Roukin}/Empire of Silence.m4b` |
| none | `Christopher Ruocchio/Empire of Silence/Empire of Silence.m4b`
|
## Testing
- `tests/core/test_narrator_template_variable.py` (new, 16 tests):
`{{Narrator}}` with and without a value, the `{ {Narrator}}` prefix
form, prefix/suffix blocks, sanitizing, `build_library_path`, task
metadata, retry payload round trip, MAM series name/number parsing
(including a `1-3` range having no number), and queueing (narrator from
`extra`, series fallback, no cross-series number, blank narrator).
- `namingTemplatePreview.test.ts`: the same `{{Narrator}}` cases for the
preview.
- Updated `test_generate_env_docs.py` for the new template description.
- Full `pytest -m "not integration and not e2e"` compared with an
upstream `main` worktree on the same machine: no new failures (the
remaining ones are Windows-only on both).
- `ruff check`, `ruff format --check`, `basedpyright` (0 errors) and
`vulture` on touched files; frontend `tsc --noEmit`, `oxlint`, `oxfmt
--check`, `vitest` (218 passed).
- Checked the settings page locally: the audiobook preview renders `...
{Simon Vance}.mp3` and `{Narrator}` is listed for audiobook templates
only.
No behavior change for existing templates, apart from spaces next to `/`
being trimmed.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
61 lines
1.9 KiB
Python
61 lines
1.9 KiB
Python
from scripts.generate_env_docs import generate_env_docs
|
|
|
|
|
|
def test_generated_env_docs_use_canonical_mirror_env_vars() -> None:
|
|
docs = generate_env_docs()
|
|
|
|
for canonical_var in (
|
|
"AA_BASE_URL",
|
|
"AA_MIRROR_URLS",
|
|
"LIBGEN_MIRROR_URLS",
|
|
"ZLIB_MIRROR_URLS",
|
|
"WELIB_MIRROR_URLS",
|
|
):
|
|
assert f"`{canonical_var}`" in docs
|
|
|
|
for legacy_var in (
|
|
"AA_ADDITIONAL_URLS",
|
|
"LIBGEN_ADDITIONAL_URLS",
|
|
"ZLIB_PRIMARY_URL",
|
|
"ZLIB_ADDITIONAL_URLS",
|
|
"WELIB_PRIMARY_URL",
|
|
"WELIB_ADDITIONAL_URLS",
|
|
):
|
|
assert f"`{legacy_var}`" not in docs
|
|
|
|
assert "https://annas-archive.gl" not in docs
|
|
|
|
|
|
def test_generated_env_docs_describe_mirror_lists_as_comma_separated_strings() -> None:
|
|
docs = generate_env_docs()
|
|
|
|
assert (
|
|
"| `AA_MIRROR_URLS` | List the Anna's Archive mirror URLs you want Shelfmark to use. "
|
|
"Type a URL and press Enter to add it. Order matters when Auto is selected. | "
|
|
"string (comma-separated) | _empty list_ |"
|
|
) in docs
|
|
assert (
|
|
"| `LIBGEN_MIRROR_URLS` | Mirrors are tried in the order you add them until one works. | "
|
|
"string (comma-separated) | _empty list_ |"
|
|
) in docs
|
|
|
|
|
|
def test_generated_env_docs_include_custom_component_value_fields() -> None:
|
|
docs = generate_env_docs()
|
|
|
|
for env_var in (
|
|
"TEMPLATE_RENAME",
|
|
"TEMPLATE_ORGANIZE",
|
|
"TEMPLATE_AUDIOBOOK_RENAME",
|
|
"TEMPLATE_AUDIOBOOK_ORGANIZE",
|
|
):
|
|
assert f"`{env_var}`" in docs
|
|
|
|
assert (
|
|
"| `TEMPLATE_AUDIOBOOK_ORGANIZE` | Use / to create folders. Variables: "
|
|
"{Author}, {FirstAuthor} (first of several authors), {Title}, {Year}, {Language}, "
|
|
"{User}, {OriginalName} "
|
|
"(source filename without extension), {Series}, {SeriesPosition}, {Subtitle}, "
|
|
"{PrimaryTitle}, {PartNumber}, {Narrator}. Use arbitrary prefix/suffix:"
|
|
) in docs
|