Skip to content

Library scan attributes every book by an author to whichever one is scanned, and sets the author folder as its BasePath #765

Description

@m4bard

Summary

When an audiobook has no BasePath, a library scan attributes every audio file by that author to it, and then stores the author folder as the book's BasePath.

Because BasePath is what rename and move operate on, a book in this state has an entire author's shelf as its folder.

Cause

ScanFileDiscovery.Matches accepts "the path contains the author" as sufficient to attribute a file to a specific audiobook:

var filePathMatchesAuthor = !string.IsNullOrEmpty(authorToken)
    && file.Contains(authorToken, StringComparison.OrdinalIgnoreCase);

return fileNameMatchesTitle || filePathMatchesAuthor || directoryMatchesTitle;

In an {Author}/... layout — which is most of them — every file beneath an author's folder contains that author's name. So the author clause is true for every one of them.

FindMatchingAudioFiles groups candidates by directory and takes all files in any group where any file matches, so each of the author's book folders is claimed whole. ScanPathPlanner.CalculateBasePath then reduces the result to its common parent — the author folder.

The author identifies a shelf, not a book.

How it is reached

ScanJobProcessor uses audiobook.BasePath as the scan root when it is set. When it is empty, the root falls back to the configured OutputPath — the whole library — and every audio file under it becomes a candidate.

A BasePath can be empty in normal use: it is only assigned on add when a destination path is supplied, and ScanJobProcessor explicitly sets audiobook.BasePath = null when the stored folder no longer exists. The next scan of that book then runs against the whole library.

Reproduction

A conventional layout, three public-domain books by one author:

{root}/H. Rider Haggard/H. Rider Haggard - She/She.m4b
{root}/H. Rider Haggard/H. Rider Haggard - Allan Quatermain/Allan Quatermain.m4b
{root}/H. Rider Haggard/H. Rider Haggard - King Solomon's Mines/King Solomon's Mines.m4b

Scanning the audiobook She (no BasePath, so the scan root is the library root):

expected: ["She.m4b"]
actual:   ["Allan Quatermain.m4b", "King Solomon's Mines.m4b", "She.m4b"]

BasePath expected: .../H. Rider Haggard - She
BasePath actual:   .../H. Rider Haggard          <-- the author folder

ScanFileDiscovery.FindMatchingAudioFiles is a static taking a root path and an Audiobook, so this reproduces in a unit test against a temp directory — no database or container needed.

Suggested fix

Only the title can attribute a file to a specific book. Author-in-path should not produce a match on its own.

A path carrying neither the title nor anything else identifying is then simply not attributable by path, and is left unmatched rather than attached to an arbitrary book by the same author. That is the right outcome: a miss is recoverable, a wrong link is not — and the user has no reason to go looking for it.

This does mean layouts that encode neither the title nor the author in the path (AudioBookShelf-style series folders with numbered episode files) go from wrongly matched to unmatched. Those are exactly what an embedded-tag pass should claim, which is what #688 proposes — the two changes complement each other.

Notes

No existing test depends on author-only matching; the full suite passes unchanged with the author clause removed.

Books cited are public domain with real Audible editions, so the case is reproducible by anyone.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions