From 7c8e98917d2be56b549f4db797a98c91c9921f99 Mon Sep 17 00:00:00 2001 From: Audiolib Date: Tue, 26 May 2026 20:54:41 +0200 Subject: [PATCH] Show cover thumbnail + chapter count in match search results Backend: After the parallel search, fetch get_release_details for the top-3 MusicBrainz hits in parallel. MB's search response carries neither cover_url nor tracklist, so without this nothing useful would show for MB results. Other sources already include cover in their search response and don't have chapter data anyway. Adds chapterCount to every result (0 when unknown). For MB matches that resolve to a release with a tracklist, this is the actual count that would be created as Chapters on apply. UI: Match results now render as a row with a 48px cover thumbnail on the left, title + metadata in the middle, Apply button on the right. Metadata line shows author, year, source, confidence, and chapter count (highlighted in green when present). Broken cover URLs hide gracefully via onError. Co-Authored-By: Claude Sonnet 4.6 --- backend/app/services/matcher.py | 19 +++++++++++++++++++ frontend/src/pages/BookDetail.tsx | 30 ++++++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/backend/app/services/matcher.py b/backend/app/services/matcher.py index fc2e420..5faad5d 100644 --- a/backend/app/services/matcher.py +++ b/backend/app/services/matcher.py @@ -373,6 +373,24 @@ async def search_for_item(title: str, author: str | None = None) -> list[dict]: _search_source("dnb", search_dnb(search_title, author)), ) + # MusicBrainz: Search liefert weder Cover noch Tracklist. + # Für die Top-3 MB-Treffer Details holen, damit Cover + Kapitelzahl im UI sichtbar sind. + mb_top = sorted(mb, key=lambda r: r.confidence, reverse=True)[:3] + if mb_top: + async def _details(mb_result): + try: + return await get_release_details(mb_result.source_id) + except Exception as e: + logger.warning(f"MB-Details Fehler für {mb_result.source_id}: {e}") + return None + details = await asyncio.gather(*(_details(r) for r in mb_top)) + for orig, detail in zip(mb_top, details): + if detail: + if detail.cover_url and not orig.cover_url: + orig.cover_url = detail.cover_url + if detail.chapters and not orig.chapters: + orig.chapters = detail.chapters + results = [] for r in mb + ol + gb + dnb: results.append({ @@ -390,6 +408,7 @@ async def search_for_item(title: str, author: str | None = None) -> list[dict]: "language": r.language, "genres": r.genres, "cover": r.cover_url, + "chapterCount": len(r.chapters or []), "confidence": r.confidence, }) diff --git a/frontend/src/pages/BookDetail.tsx b/frontend/src/pages/BookDetail.tsx index 7498420..8fa1913 100644 --- a/frontend/src/pages/BookDetail.tsx +++ b/frontend/src/pages/BookDetail.tsx @@ -283,13 +283,39 @@ export default function BookDetail() { )} {matchResults.map((r, i) => (
+
+ {r.cover ? ( + { (e.currentTarget as HTMLImageElement).style.display = 'none' }} + /> + ) : ( + + )} +

{r.title}

-

{r.author} · {r.source} · {Math.round(r.confidence * 100)}%

+

+ {[ + r.author, + r.publishYear, + r.source, + `${Math.round(r.confidence * 100)}%`, + ].filter(Boolean).join(' · ')} + {r.chapterCount > 0 && ( + · {r.chapterCount} Kapitel + )} +