Skip to content

Reduce rendered folder names in title tags to the bare title - #857

Open
krejko wants to merge 2 commits into
Listenarrs:canaryfrom
nexalapp:feat/normalize-rendered-titles-from-tags
Open

Reduce rendered folder names in title tags to the bare title#857
krejko wants to merge 2 commits into
Listenarrs:canaryfrom
nexalapp:feat/normalize-rendered-titles-from-tags

Conversation

@krejko

@krejko krejko commented Aug 20, 2026

Copy link
Copy Markdown

Product decision — close this if you disagree. Whether an embedded tag should ever be normalised is a judgement call that belongs to you, not to me. I think the evidence below makes it the right call, but I've kept it deliberately narrow so it's easy to reject without affecting anything else. Stacked on #856 — please review that first; this branches from it.

Summary

Taggers routinely write the whole rendered folder name into the album tag. A book organised as [Revelation Space 10] Dilation Sleep (1990) carries:

TAG:album=[Revelation Space 10] Dilation Sleep
TAG:title=Dilation Sleep

ApplyEmbeddedTags copies that verbatim, and library import sends it to search as the title.

The metadata lookup is a text search against the Audible storefront, so a decorated query matches nothing. Measured on a real instance:

Query sent Result Time
[Revelation Space 10] Dilation Sleep 0 results 15s
Dilation Sleep resolves

The failure is silent — search returns nothing, and the user sees matching fail on books that are in the catalogue. This is the single most common reason "Start Matching" appears broken on an otherwise well-tagged library.

Why normalising is defensible

The narrow argument: this only un-renders a value that was rendered from the user's own pattern. It runs the tag through the same pattern-derived regex added in #856, and only rewrites when the value parses as that pattern and carries pattern markers. A tag that is already a plain title carries no markers and is returned untouched.

So the rule is not "clean up tags" — it's "if the tag is literally this library's folder name, treat it as one".

Crucially it does not change tag-versus-path precedence. Tags still win for every field, including title. Nothing is reordered.

The counter-argument

Tags are user data, and silently rewriting them is a real cost. If you'd rather the app never second-guess a tag, this should be closed — I'd rather that than argue it into the codebase. Two alternatives if you like the outcome but not the mechanism:

  • do it in buildLibraryImportSearchParams so only the query is cleaned and displayed metadata stays verbatim;
  • put it behind a setting, defaulted off.

I'd lean against the frontend option only because the pattern lives server-side and duplicating the derivation in TypeScript invites drift.

Changes

Added

  • NamingPatternFolderMatcher.ExtractTitle — returns the bare title when a value parses as the configured folder pattern, otherwise the input unchanged.
  • Tests for decorated values, undecorated values, and the no-pattern-configured case.

Changed

  • ApplyEmbeddedTags routes the incoming title through it.

Testing

30/30 pass.

Verified end-to-end. Titles reaching search, before → after:

Before After
[Revelation Space 10] Dilation Sleep Dilation Sleep
[Known Space 00.0] Beclaimed in Hell Beclaimed in Hell
[Enderverse 07.5][Ender's Saga 1.1] A War Of Gifts {Scott Brick} (2007) A War Of Gifts
[Radicalized] Radicalized Radicalized

Undecorated titles (Dilation Sleep, The Garden of Rama, Harry Potter and the Sorcerer's Stone) are asserted to pass through unchanged, and with no configured pattern the input is always returned as-is.

Three books that previously failed to match resolved immediately afterwards, including one where the catalogue title corrected a typo in the folder name (BeclaimedBecalmed in Hell).

Notes

No-ops entirely when FolderNamingPattern is unset. Regex is cached and timeout-guarded; RegexMatchTimeoutException returns the input unchanged.

🤖 Generated with Claude Code

krejko and others added 2 commits August 20, 2026 15:44
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>
Taggers commonly write the whole rendered folder name into the album/title tag,
so a file organised as "[Revelation Space 10] Dilation Sleep (1990)" carries
album="[Revelation Space 10] Dilation Sleep". ApplyEmbeddedTags copies that
verbatim, and library import then sends it to search as the title.

The metadata search is a text search against the Audible storefront, so a
decorated query matches nothing: "[Revelation Space 10] Dilation Sleep" returns
zero results where "Dilation Sleep" resolves. Users see matching fail on books
that are present in the catalogue, and the failure is silent - the search simply
returns nothing.

When such a tag value parses as the configured folder naming pattern, reduce it
to its title. Values that do not parse as the pattern, and values carrying no
pattern markers, are returned untouched, so ordinary titles are unaffected.

This deliberately does not change tag-versus-path precedence: tags still win for
every field. It only un-renders a value that was rendered from the user's own
pattern in the first place.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@krejko
krejko requested a review from a team August 20, 2026 21:50
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