Skip to content

Fix: narrator custom formats never match "Title (Narrator Name)" releases - #56

Open
JordanFromIT wants to merge 1 commit into
Chaptarr:developfrom
JordanFromIT:fix/narrator-parenthetical-extraction
Open

Fix: narrator custom formats never match "Title (Narrator Name)" releases#56
JordanFromIT wants to merge 1 commit into
Chaptarr:developfrom
JordanFromIT:fix/narrator-parenthetical-extraction

Conversation

@JordanFromIT

@JordanFromIT JordanFromIT commented Aug 16, 2026

Copy link
Copy Markdown

Description

Why this matters: when a work has more than one narration — Jim Dale vs. Stephen Fry, Roy Dotrice vs. John Lee — narrator custom formats are how you tell Chaptarr which one you actually want. Today that silently does nothing for one of the most common ways indexers credit a narrator, so two book rows pinned to different narrators come back with the same releases in the same order, and you get whichever one happens to sort first. The naming tokens already put the two narrations in separate folders once they land; this is about grabbing the right one in the first place.

The narrator matching machinery is already here and already wired up — ReleaseNarratorEvidenceExtractor, PreferredNarratorMatcher, the three specifications, the enricher, the naming tokens. This connects one last pipe.

ReleaseNarratorEvidenceExtractor recognises three shapes of narrator credit in a release name:

Pattern Example
read by / narrated by / narrator: ... narrated by Roy Dotrice
trailing square brackets 1984 [Andrew Wincott]
trailing dash 1984 - Andrew Wincott

There is no pattern for a trailing parenthetical, which is one of the most common forms in the wild:

George R.R. Martin - A Feast for Crows (Roy Dotrice)

The bracket pattern needs square brackets, and the trailing-dash pattern explicitly excludes parentheses from its character class — so this title produces no narrator evidence at all. Because all three narrator specifications read from the same extractor, Selected Audiobook Narrators, Narrator Names and Narrator (Advanced) all score zero on it. The narrator is right there in the title and nothing matches it.

The fix adds the trailing parenthetical as an unlabelled candidate pattern. It routes through the existing IsKnownAuthor rejection and plausibility gate, so the author-vs-narrator guard applies unchanged.

Parentheses are also where release names put years, formats and bitrates, so a candidate found there must additionally look like a person's name — two to six tokens, all alphabetic:

Candidate Result
(Roy Dotrice) narrator
(2011) rejected — single token
(M4B-64) rejected — m4b, 64 are not alphabetic
(Retail MP3) rejected — mp3 is not alphabetic
(Unabridged) rejected — single token

Matching a name the book already asks for stays authoritative and bypasses the shape check, so a requested narrator is never discarded for having an unusual name.

Also fixed: the explicit-narrator pattern's capture class did not exclude ) or ], so a labelled credit inside parentheses swallowed the closing bracket and any trailing year — (Narrator Julia Whelan 2019) yielded the literal string Julia Whelan 2019). It now yields Julia Whelan.

Known limitation, stated plainly: a two-word alphabetic parenthetical that is not a narrator — a publisher like (Brilliance Audio) — will be collected as evidence. It is inert, because evidence only scores when NarratorNameMatcher ties it to a configured or preferred name, and nobody configures a narrator by that name. The existing [...] and - ... patterns already carry this same class of risk. I kept the new pattern consistent with them rather than inventing a stricter mechanism only parentheses would use.

Deliberately not in this PR: en.json carries NarratorFuzzyMatching, NarratorMatchThresholdPercent and NarratorMatchingSettings strings with no code referencing them, and the shipped matcher has no threshold concept. Wiring a configurable threshold is a behaviour design decision that is yours to make, not something to smuggle into a parsing fix. Happy to open a separate issue or PR if you want it.

Fixes # N/A — found while testing narrator custom formats against live indexer results.

Database Migration

NO.

How was this tested?

Unit tests — added to the existing AudioProductionCustomFormatFixture next to the current narrator cases:

  • preferred_narrator_spec_should_match_trailing_parenthetical_narrator
  • narrator_names_spec_should_match_trailing_parenthetical_narrator
  • parenthetical_release_metadata_should_not_be_extracted_as_a_narrator — 6 cases covering year, bitrate, format and --FIXED--
  • explicit_narrator_label_should_not_capture_the_closing_parenthesis_or_year

Each was written first and observed failing for the right reason before the change. The junk-metadata cases genuinely fail against a naive parenthetical pattern (M4B-64, Retail MP3 and 64 kbps all leak through), which is what forces the name-shape gate.

Full suite, Debug and Release, on Linux with .NET 10:

dotnet test src/Chaptarr.Core.Test/Chaptarr.Core.Test.csproj --configuration Release
develop (537eb64): 2834 passed, 0 failed
this branch:       2843 passed, 0 failed   (+9, the cases above)

dotnet build src/Chaptarr.NoTests.sln --configuration Release — 0 warnings, 0 errors. The build.yml guard steps pass as well: conflict markers, package.json parse, and version_guard.py in sync, monotonic and commit-hygiene modes.

End-to-end, because unit tests alone do not prove the wiring. Two throwaway containers from chaptarr/chaptarr:0.9.929, identical config, differing only by the patched Chaptarr.Core.dll, both pointed at a local mock Torznab indexer serving synthetic release names, with a Narrator Names custom format scored 100 for a single narrator. Interactive search on the same book:

Release title stock patched
George Orwell - 1984 (Andrew Wincott) 0 100
George Orwell - 1984 (2011) 0 0
George Orwell - 1984 (M4B-64) 0 0
George Orwell - 1984 (Retail MP3) 0 0
George Orwell - 1984 [Simon Prebble] 0 0

The intended release scores, the metadata parentheticals stay silent, and the existing bracket form is unaffected.

Note that ReleaseInfo.Narrator is still unset for these releases — this PR only feeds the custom format evidence extractor. Populating the release resource so the interactive search grid's narrator column has something to show is a separate, larger change and is not attempted here.

Screenshots (UI changes only)

None — no UI changes.

Release titles commonly credit the narrator in a trailing parenthetical
("Author - Title (Roy Dotrice)"), but no extraction pattern covered that
form. The bracket pattern requires square brackets and the trailing-dash
pattern excludes parentheses, so such titles yielded no narrator evidence
and every narrator custom format scored zero on them.

Parentheses also hold years, formats and bitrates, so candidates found
there must additionally look like a person's name: two to six tokens, all
alphabetic. That rejects "(2011)", "(M4B-64)" and "(Retail MP3)" while
accepting "(Roy Dotrice)". Matching the book's requested narrator stays
authoritative and bypasses the shape check.

Also stop the explicit narrator pattern from capturing a closing bracket
or a trailing year, so "(Narrator Julia Whelan 2019)" yields
"Julia Whelan" rather than "Julia Whelan 2019)".

This affects all three narrator specifications, which share the same
evidence extractor.
@JordanFromIT JordanFromIT changed the title Extract narrator credits from trailing parentheses in release titles Fix: narrator custom formats never match "Title (Narrator Name)" releases Aug 16, 2026
@JordanFromIT

Copy link
Copy Markdown
Author

Additional verification while testing this against a book with a lot of real narrations — it turns out the impact is broader than I described above, so I wanted to record it.

I set up a book with 116 audiobook editions, scored the built-in Selected Audiobook Narrators custom format at 100, and pinned the book to one narrator's edition via POST /book/{id}/editions/wanted, then re-pinned it to a different narrator. Same mock indexer serving the same 8 synthetic releases both times; the two instances are identical apart from this patch.

Pinned edition's narrator Release before after
Andrew Wincott 1984 (Andrew Wincott) 0 100
Andrew Wincott 1984 (Stephen Fry) 0 0
Stephen Fry 1984 (Stephen Fry) 0 100
Stephen Fry 1984 (Andrew Wincott) 0 0

Before the patch every release scores 0 under both pins — so for releases that credit the narrator in parentheses, PreferredNarratorSpecification and the pre-seeded Selected Audiobook Narrators format it backs never fire at all. Pinning an edition has no effect on scoring. After the patch the score correctly follows whichever edition is pinned.

That felt worth adding because it means this is not only a parsing edge case: a built-in custom format that ships enabled is inert on a very common release-naming style, and the edition-pinning flow it supports silently does nothing. No change to the diff — the fix is the same, I had just understated what it unblocks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant