Parse book folders using the configured naming pattern - #856
Open
krejko wants to merge 1 commit into
Open
Conversation
Folder rendering is configurable through FolderNamingPattern, but folder parsing
is hardcoded to a single convention:
BookFolderPattern = ^(\d{4})\s+-\s+(.+?)(?:\s+\[(.+?)\s+([\d.]+)\])?$
The two disagree out of the box. The default pattern is
"{Author}/{Series}/{Title}", which renders "Brandon Sanderson/Stormlight
Archive/The Way of Kings" - and none of those segments match the parser, which
requires a leading four-digit year. A library organised by Listenarr's own
default settings cannot be read back, and any user whose pattern is not
"{Year} - {Title} [{Series} {Part}]" gets no path-derived series, title or year
at all.
Derive the folder matcher from the configured pattern instead, so parsing is the
inverse of rendering. The derivation mirrors the renderer's elision rules: a
bracket group containing only tokens disappears when all of them are empty, and
renders partially when only some are, so each token inside such a group is
independently optional when reading.
Two safeguards:
- Every token being optional means a bare directory name satisfies the pattern
structurally, so an author folder would be claimed as a book. A match is only
accepted when it carries a non-title marker (series, year, narrator, ...).
- An unparseable pattern returns no matcher rather than throwing, and both that
case and a non-matching folder fall back to the built-in convention, so
existing layouts keep working.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Folder rendering is configurable via
FolderNamingPattern, but folder parsing is hardcoded:These disagree out of the box. The default pattern is
{Author}/{Series}/{Title}, which rendersBrandon Sanderson/Stormlight Archive/The Way of Kings— and the parser requires a leading four-digit year, so none of it matches:A library organised by Listenarr's own default settings cannot be read back. Any user whose pattern isn't
{Year} - {Title} [{Series} {Part}]gets no path-derived series, title or year — scanning silently falls back to embedded tags alone, and books with no series tags simply have no series.Approach
Derive the matcher from the configured pattern, so parsing is the inverse of rendering.
The derivation mirrors the renderer's elision rules: a bracket group containing only tokens disappears when every token is empty, and renders partially when only some are. So
[{Series} {SeriesNumber}]renders[Radicalized]for an unnumbered series — meaning each token inside an elidable group must be independently optional when reading.Two safeguards:
Author/Series/book.m4bcorrectly parses to nothing.Changes
Added
NamingPatternFolderMatcher— builds and caches a regex fromFolderNamingPattern.PathMetadataParser_ConfiguredPatternTests.Changed
PathMetadataParseraccepts an optional pattern and prefers it, falling back to the built-in convention.appSettings.FolderNamingPattern(already loaded ~125 lines above the call site).Testing
21/21 pass, including the pre-existing
PathMetadataParsertests.Verified end-to-end against a real library with
{Author}/[{Series} {SeriesNumber}] {Title} {{Narrator}} ({Year}). Before, series came only from embedded tags — every book without them had none. After:[Chronicles of Narnia 0] …[Known Space 00.0] …[Enderverse 07.5][Ender's Saga 1.1] …[Radicalized] Radicalized (2019)Alastair Reynolds(author dir)Covers zero-padding, decimals, plain
0, unnumbered series, standalone books, and folders with no year.Notes
Two tolerances go slightly beyond a literal inversion, both for real-world folder noise:
[Enderverse 07.5][Ender's Saga 1.1] Title(a book in two series). First bracket wins.… (1991) [abridged], which otherwise swallows the year into the title.I've kept them because both occur in practice and neither can be expressed in a pattern, but they are judgement calls — happy to drop either and let those folders fall back if you'd prefer a strict inversion.
The regex is cached per pattern string and compiled with a 1s timeout.
🤖 Generated with Claude Code