Match scanned files when the on-disk name drops a leading article - #784
Match scanned files when the on-disk name drops a leading article#784dny238 wants to merge 1 commit into
Conversation
|
Worth flagging up front: this file is rewritten on #717. I built your branch and ran it against its own base (
The first row is a new misattribution, and it also moves The mechanism for the first is Then I built the two shapes your description is about, to check what the change buys:
Both already work on canary, so on these two shapes the tolerant matching isn't adding recall. But the second only works on canary because of the author arm: the title arm needs the full recorded title as a substring and That's the part I'd flag as worth keeping. Your So once #717 removes the author arm, article-dropped names lose the thing currently carrying them, and the title half of this is what replaces it. It's the author half — whole-path, initials stripped — that produces the misattributions above, and dropping it doesn't cost either shape I tested. Repro, if it's useful — from listenarr-testdata: It generates the library, drives a real container, and exits non-zero if the scan claims a file belonging to another book. Happy to run it against any revision you push. |
Review (@m4bard, Listenarrs#784) measured that the author arm caused misattributions: NormalizeAuthor dropped single-letter initials so "M. R. James" collapsed to {james} and subset-matched against the whole path, linking unrelated files that merely shared an author's shelf (Henry James' "The Turn of the Screw" attributed to M. R. James' "Ghost Stories of an Antiquary"). Remove the author fallback entirely and tighten the title rule to strict token-set equality after article/punctuation normalization. This keeps the case this PR targets — on-disk names that only drop a leading "The" (and is what carries article-dropped names once Listenarrs#717 removes the pre-existing author arm) — without attributing files on title alone-less grounds. Adds a regression test for the cross-book/shared-surname case. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Ran the harness against Same setup as last time. One book added,
Your own shapes, same build:
You can run this yourself rather than waiting on me: git clone https://github.com/m4bard/listenarr-testdata && cd listenarr-testdata
python3 -m venv .venv && .venv/bin/pip install -e .
./tools/vet-against.sh \
--repo https://github.com/dny238/Listenarr.git --branch fix/scan-file-match-tolerant \
--tool attribution \
--asin B004FOLXEO --layout author-title \
--only-asin B004FOLXEO,B01ATTZF38,B0C6FJ6L34That clones your branch, builds it, generates a library holding those three books, scans, and exits non-zero if the scan claimed a file belonging to another book. It needs podman or docker, plus ffmpeg and curl. The three ASINs are the M. R. James, Henry James and James M. Barrie books from the table, which are useful together because the names overlap. Anything in |
|
Thanks for the thorough repro — the Pushed 0ac2326: removed the author arm entirely and tightened the title rule to strict token-set equality after article/punctuation normalization (no more either-way subset). So the tolerant path now only fires when the on-disk name equals the recorded title modulo a leading article — e.g. I've left the pre-existing Happy to run your |
|
#717 replaces the old broad scan matcher with ownership/stable-identifier/book-boundary attribution and incorporates the #766/#765 author-overmatch fix. The tolerance in this PR (leading articles, credentials/initials, token-subset matching) is still additional behavior and is not superseded. Please rebase on #717 and add the tolerance inside the new book-boundary matching rules rather than reintroducing broad path matching. |
|
@m4bard thanks for re-running your harness against the reworked branch — good to have it confirmed that the misattributions are gone while the real-world cases still link. @therobbiedavis understood — I'll rebase this on #717 and add the tolerance (leading articles, credentials/initials, title token-set equality) inside the new book-boundary matching rules rather than reintroducing broad path matching. I'll wait for #717 to merge so I'm folding it into the final rules. The tolerance is title-only |
|
Heads up that the thing this was waiting on has landed: #717 merged on 2026-08-12, and #819 has gone in since, which moved a lot of the same area again. No pressure on the rebase from me. I mention it only because the wait was for a specific event and that event is now a week and a half old, so it seemed worth saying rather than assuming you had seen it. The harness that produced the earlier numbers still exists and still runs, so when you have the tolerance folded into the book-boundary rules I am happy to re-run it against the rebased branch the same way as before: the shared-surname cross-book case, the leading-article case, and the credentials and initials cases, reported as attributed against not attributed per case rather than as a single pass or fail. If it would be more useful to have that as a check you can run yourself rather than as a comment from me, say so and I will point you at it. |
Fold the leading-article tolerance from the previous version of this PR into Listenarrs#717's new book-boundary matcher: SegmentMatchesExpectedTitle now treats a segment and an expected title as equal when they differ only by a leading article ("The"/"A"/"An") on either side, so a folder "Language of Emotions" still attributes to the audiobook "The Language of Emotions" (and vice versa). This stays a full-title equality modulo the article -- it does not reintroduce substring or author-based matching -- so the same-author / different-book boundary guards are preserved. Covered by three new ScanFileDiscoveryTests (dropped article, added article, and a same-author sibling-book guard). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
0ac2326 to
cd29725
Compare
|
@m4bard thanks — I've pushed the reworked branch. It's rebased on canary and the leading-article tolerance is now folded into #717's book-boundary matcher ( If you're still up for re-running your harness against this branch, that'd be hugely appreciated — the shared-surname cross-book, leading-article, and credentials/initials cases, reported per-case attributed/not-attributed as before. Heads-up on expectations: this rework is deliberately title-only (it drops the author-based fallback per the earlier review), so the credentials/initials cases should now report as not attributed — that's intended, not a regression. |
|
Ran the harness against The scan-attribution check adds exactly one book, clears its
The article case does what you describe, and the boundary holdsThis is the one worth having. The book is On canary the scan links nothing at all: the book is simply not found. On your branch it links one file, its own, and That is your The shared-surname case is identical on both sides, so nothing regressed there either. The initials case is unchanged rather than brokenI want to be careful here, because I nearly reported this as a failure. Author credited as Reading the diff, that is expected: the change strips a leading article from both sides of a title comparison and says so explicitly, and the comment is clear that it does not widen into author matching. So nothing in Worth raising anyway, because the PR title still says "or author credentials" and the implementation on this revision is article-only. Whether the title should narrow or the credentials half is still to come is your call, but as it stands somebody reviewing against the title will look for something that is not in the diff. One caveat on that case: I chose full given names against initials as my reading of "credentials/initials". If you meant an honorific or a suffix, How to reproduceThe two prepared cases are not a checked-in scenario, so this is the honest recipe rather than one command. Both start from the public corpus in the test-data repo. For the article and initials cases: generate a two-book library with I am going to add those two variants to the generator so they stop being hand-built, and I will |
|
Following up on the last paragraph of my earlier comment: the two cases are no longer hand-built, so here are the single commands. Swap
It sits on the generator's hazard axis rather than being a layout, because a layout is uniform and this case needs one book varied beside one that is not. A variant a book cannot express is recorded as unapplied rather than claimed, so The numbers are the same ones I reported before, which is how I checked the generated tree matches what I had built by hand: article case, canary links nothing and your branch links one own file with none foreign; initials case, nothing on either. Two things I got wrong the first time, in case you hit them. |
|
@m4bard thank you — running it at runtime against canary on the same library is exactly the confirmation I couldn't get from the diff, and I appreciate the care on the initials case (calling it "unchanged, not a regression" rather than quietly a pass). You're right about the title, and I've retitled the PR to match the diff: it's now "Match scanned files when the on-disk name drops a leading article". This revision is deliberately article-only — I dropped the author/credentials arm during the earlier review because the surname-based matching was the source of the cross-book misattributions you originally caught, so keeping it strictly full-title-modulo-article is what makes the boundary hold. On what "credentials" was meant to cover: author-folder differences like dropped post-nominals ( Thanks also for folding the two variants into the generator and posting the single commands — that's a genuinely useful harness. |
|
The retitle matches the diff now, and I think dropping the author arm was the right call rather than a concession. The failure I measured before came from surname-based matching pulling in the wrong book, and full-title-modulo-article does not have that failure mode: the title still has to match in full, so there is no room for a near-miss to win. Keeping the two apart is what makes this one reviewable on its own. On the follow-up, and what I can actually offerYes, and I would rather be precise about what exists so you are not waiting on something I have not built. The generator in the harness currently has two folder variants: So the initials-versus-full-name half of what you described is ready now. Post-nominals are not: there is no One thing worth deciding before you write it, since it bit the earlier version. Initials tolerance and post-nominal tolerance are not the same risk. Stripping I have no opinion on whether you split them, only that the initials half is the one that needs the evidence. The harnessGlad it is useful. Both variants are on the public generator, so anything I run against your branch is reproducible from the same commands rather than from my library. If it would help, say which ASINs you want them applied to and I will post the exact invocation alongside the result. Worked through with Claude Code at my direction. The variant list above is read from the generator as it stands today, and the post-nominal gap is stated because it is not there yet. I reviewed this before posting. |
Problem
The per-audiobook filesystem scan (
ScanFileDiscovery.FindMatchingAudioFiles) links a file to the book only when the filename contains the full library title or the path contains the full author string (case-insensitive substring). That misses very common real-world naming:Language of Emotions.m4bfor the book The Language of Emotions.Karla McLaren\for author M.Ed. Karla McLaren, orJohn M. Gottman\vs library authorJohn Gottman **PhD**.Developing Mind\for The Developing Mind, Third Edition.In these cases the audio file sits on disk but the scan matches nothing, so the book stays fileless and Scan Folder appears to do nothing even though the file is right there.
Fix
Adds tolerant, token-based matching on top of the existing exact checks — purely additive, so any file that matched before still matches:
the/a/an) and punctuation/subtitles normalized away.PhD,MD,M.Ed.,CFP,Jr, …) and single-letter initials dropped.Matching is scoped to the audiobook's own scan folder (
scanRoot = audiobook.BasePath), so the extra leniency can't pull in unrelated books.Tests
New
ScanFileDiscoveryMatchTestscovering: exact-title baseline (unchanged), dropped leading "The", author-with-credentials, author-with-middle-initial, title-with-subtitle vs base-title folder, and a negative case (unrelated file must not match).dotnet buildclean (0 warnings)🤖 Generated with Claude Code