DNB rewrite:
- Multiple query strategies with fallback (title+author+mat=ton →
title+author → title+mat=ton → title-only → fulltext). Returns on
first hit. Most German audiobooks aren't tagged mat=ton in DNB,
which was killing all searches.
- Strip CQL wildcard chars (?, *, <, >, =, /, quotes) from search
terms. The "???" in "Die drei ???" was breaking the CQL parser.
- Log HTTP status, body snippet on non-200, and numberOfRecords on
every query so log shows exactly what DNB returned.
- Parse SRU diagnostic elements (DNB error messages buried in XML).
- Convert author/narrator from "Lastname, Firstname" to
"Firstname Lastname" for consistency with other sources.
Matcher:
- Split series patterns: WITH_EPISODE (need digit) and SERIES_ONLY
(just the series name). "Die drei ??? und der Fluch des Rubins"
now properly detects "Die drei ???" as series even without folge#.
- New _build_search_title: removes ??? sequences, trailing parens,
collapses whitespace, before sending to APIs.
- Manual search also passes through normalization. Logs source +
hit count per query.
Debug endpoint:
- GET /api/items/match/debug?title=...&author=... returns raw results
from all 4 sources with status, error messages, and full metadata.
- "Debug" button added in BookDetail — shows what each API actually
returns inline, so the user can see if it's a search problem,
parse problem, or threshold problem.
- "Cover aus Datei" button — triggers local cover extraction
(folder.jpg or embedded artwork) on demand.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Streaming: Custom range-aware HTTP endpoint. Returns 206 Partial Content
for Range requests (with Content-Range, Content-Length, Accept-Ranges).
This was the root cause of broken seeking — Starlette's default
FileResponse behavior wasn't reliable across all clients. Now seeking
works natively via standard HTML5 audio.
Player: Full rewrite. Cleaner separation between absolute book time and
per-track time. Track switching uses pendingSeek + canplay/loadedmetadata
handlers. Console logs for debugging. Removed crossOrigin to avoid CORS
issues. Removed hls.js entirely.
Matcher: Critical bug fix — get_work_details (OpenLibrary) was returning
a sparse MatchResult that REPLACED the rich search result, losing cover,
author, year. New _enrich_match merges details into best without
overwriting existing values (except description/chapters which are
preferred from details fetch).
Scoring: Lenient min/max-weighted similarity (better for German episodic
titles like "Die drei ??? - Folge 215"). Thresholds lowered:
UNCERTAIN 0.50→0.40, AUTO_ACCEPT 0.75→0.65.
Search: search_for_item now returns ALL fields (narrator, publisher,
series, genres, description, language) so manual apply has full data.
Apply: apply_match now always constructs from body first, then enriches
with details. Previously OL applies would lose cover/author. Added
detailed logging across matcher and apply paths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- MatchResult was missing subtitle field, causing AttributeError in
_apply_match that silently killed every background match task
- Wrap _apply_match in try/except with exc_info logging so failures
are visible in docker compose logs backend
- New POST /api/libraries/:id/match-all endpoint to trigger matching
for all unlocked items (useful for items scanned before the fix)
- Admin UI: Match button per library next to the Scan button
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- auth: username lookup is now case-insensitive via func.lower()
- scanner: trigger match_audiobook for each newly found item after scan
- matcher: read match_sources from library settings; refactored to loop
over configured sources in priority order instead of hardcoded sequence
- schemas/routers: expose matchSources in LibraryOut API response
- Admin UI: pill-toggle for MusicBrainz/Open Library/Google Books per library
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>