Skip to content

fix(naming): don't lose a series position that isn't a plain number#763

Open
m4bard wants to merge 1 commit into
Listenarrs:canaryfrom
m4bard:fix/series-position-not-lost
Open

fix(naming): don't lose a series position that isn't a plain number#763
m4bard wants to merge 1 commit into
Listenarrs:canaryfrom
m4bard:fix/series-position-not-lost

Conversation

@m4bard

@m4bard m4bard commented Jul 14, 2026

Copy link
Copy Markdown

Summary

Fixes #764.

Audnexus reports a series position as a string, and it is not always a number. Squeezing it through a decimal lost it in two ways, and the loss reached the filename.

Real positions from the catalogue (public domain, verifiable against api.audnex.us):

Book ASIN position
The Father Brown Collection: Books 1-4 B0F84DFZ66 "1-4" — one ASIN, four books
The Thirty-Nine Steps B002V1PLZK "1-2" — the product also ships Greenmantle
She and Allan B00CQ5WAXW "0" — a prequel slot
a novella between two books "1.5"

The parse used the server's culture. decimal.TryParse was called with no CultureInfo, while FileNamingService formatted the result back with InvariantCulture. The source always uses . as the decimal separator, so where . is the group separator the number changed:

en-US      "1.5" -> 1.5     "0.5" -> 0.5
de-DE      "1.5" -> 15      "0.5" -> 5
fr-FR      "1.5" -> null    "0.5" -> null
invariant  "1.5" -> 1.5     "0.5" -> 0.5

A position that didn't parse was silently discarded. It became null — indistinguishable from a book with no position — so naming fell through to the track number and wrote that into the filename instead. No error, nothing in the log.

Changes

Added

  • AudioMetadata.SeriesPositionRaw — the series position exactly as the metadata source gave it. SeriesPosition (decimal?) is kept for callers that sort or compare, but it cannot hold "1-4" and no longer pretends to.
  • SeriesPositionReproTests — 10 cases covering culture handling and non-numeric positions.

Changed

  • Audiobook.CreateBasicAudioMetadata and DownloadImportService now parse the position with InvariantCulture.
  • Both naming sites (FileNamingService.Helpers, DownloadImportService) prefer SeriesPositionRaw, then the invariant-formatted decimal, then the track number. DownloadImportService previously formatted with a bare .ToString(), which would write 1,5 — with a comma — into a filename.

Fixed

  • A non-numeric but real series position is no longer replaced by the track number when a file is renamed.
  • A decimal position no longer changes value depending on the server's locale.

Testing

SeriesPositionReproTests (10 new cases):

  • a decimal position survives under en-US, de-DE and fr-FR
  • a decimal position is written invariantly, so no locale decimal-comma reaches a filename
  • "1-4" and "1-2" are preserved and reach the filename rather than being replaced by the track number
  • a book with no position still falls back to the track number, as before
  • "0" survives

dotnet test: 1199 passing, 0 failing (1189 existing + 10 new). No existing test was changed.

Notes

The fall back to the track number is deliberate and is preserved — a book with genuinely no series position still gets it. The bug was that a real position was being treated as an absent one.

Apologies for opening this before the issue; #764 has the write-up. I've read CONTRIBUTING.md properly now.

Out of scope but worth flagging while it's in view: a book can hold two series memberships at once. Audnexus gives She and Allan (B00CQ5WAXW) seriesPrimary = Ayesha #0 and seriesSecondary = Allan Quatermain #7. MetadataConverters reads both, but AudiobookSeriesMembership is never constructed outside tests, so neither is persisted. Happy to open that separately if useful.

Audnexus reports a series position as a string, and it is not always a
number. An omnibus sits at "1-4" (The Father Brown Collection: Books 1-4),
a bundled edition at "1-2" (The Thirty-Nine Steps, which also ships
Greenmantle), a prequel at "0" (She and Allan), a novella at "1.5".

Two problems followed from squeezing that string through a decimal.

Culture. decimal.TryParse was called without a CultureInfo, so it used the
server's culture, while FileNamingService formatted the result back with
InvariantCulture. The source value always uses '.' as the decimal
separator, so under a culture where '.' is the group separator a position
of "1.5" parsed as 15, and "0.5" as 5; under fr-FR it did not parse at all.
DownloadImportService was worse: it formatted with a bare ToString(), which
would write "1,5" -- with a comma -- into a filename. Both parses are now
pinned to InvariantCulture and both naming sites format invariantly.

Lost positions. A position that failed to parse became null, which is
indistinguishable from a book that has no series position. Naming then fell
through to the track number and wrote it into the filename as if it were
the series number -- so a book at position "1-4" was silently renamed using
an unrelated number, with no error and nothing in the log.

AudioMetadata now carries SeriesPositionRaw, the position exactly as the
source gave it, and naming prefers it over the parsed decimal. The decimal
is kept for callers that need to sort or compare. The existing fall back to
the track number is deliberate and is preserved for books that genuinely
have no series position; the bug was that a real position was being treated
as an absent one.

Tests cover the culture handling under en-US, de-DE and fr-FR, that a range
position survives to the filename rather than being replaced by the track
number, and that a book with no position still falls back as before.
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.

Series position from Audnexus is lost when it isn't a plain number (locale-dependent parse; non-numeric positions dropped into the track number)

1 participant