Files
Audiolib/backend/app/services/matching/open_library.py
Audiolib 52c10a7518 Phase 5-9: Matching-Engine, Podcast-Support, Web-Interface + Player
Backend:
- Matching-Orchestrator mit deutschen Serien-Patterns (drei ???, TKKG, ...)
- Vollständige MusicBrainz-Integration (Tracklist → Kapitel, Cover Art Archive)
- OpenLibrary + Google Books als Fallback-Quellen
- Auto-Accept (≥0.75) vs zu_prüfen (0.5-0.75) vs kein Match
- Manuelles Matching: GET /api/items/:id/match/search, POST apply
- RSS-Feed-Manager: feedparser, iTunes Search, periodisches Update
- APScheduler für Podcast-Feed-Updates (konfigurierbares Intervall)
- Podcast-Router: Feed-URL setzen, Episoden, Feed-Suche
- HLS: FFmpeg läuft als Background-Task, wartet auf ersten Segment
- main.py: APScheduler + neue Router eingebunden

Frontend (React + Vite + Tailwind + HLS.js):
- Login-Seite mit Fehlerbehandlung
- Library-Seite: Grid/Listen-Ansicht, Suche, Tag-Filter, Pagination, Scan
- BookCard: Cover, Fortschrittsbalken, zu_prüfen Badge, Quick-Play
- BookDetail: Metadaten, Matching-Panel, Kapitel-Liste, Lesezeichen
- AudioPlayer: HLS.js, Kapitel-Marker auf Fortschrittsbalken, Speed,
  Sleep-Timer, Lesezeichen, Keyboard-Shortcuts (Space/Arrows)
- MiniPlayer: persistent an Fußzeile, expandierbar
- PodcastDetail: Feed-URL, iTunes-Suche, Episoden-Liste
- Admin-Panel: Benutzer/Bibliotheken/Einstellungen verwalten
- App.tsx: React Router, Auth-Guard, Player-Overlay

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 13:11:04 +02:00

60 lines
1.8 KiB
Python

import httpx
from .base import MatchResult
OL_BASE = "https://openlibrary.org"
async def search_open_library(title: str, author: str | None = None) -> list[MatchResult]:
params: dict = {"title": title, "limit": 5, "fields": "key,title,author_name,first_publish_year,cover_i,subject"}
if author:
params["author"] = author
async with httpx.AsyncClient(timeout=12) as client:
try:
r = await client.get(f"{OL_BASE}/search.json", params=params)
r.raise_for_status()
data = r.json()
except Exception:
return []
results = []
for doc in data.get("docs", []):
cover_url = None
if doc.get("cover_i"):
cover_url = f"https://covers.openlibrary.org/b/id/{doc['cover_i']}-L.jpg"
results.append(MatchResult(
source="open_library",
source_id=doc.get("key", ""),
title=doc.get("title", title),
author=doc.get("author_name", [None])[0] if doc.get("author_name") else None,
publish_year=doc.get("first_publish_year"),
cover_url=cover_url,
genres=doc.get("subject", [])[:5],
confidence=0.55,
))
return results
async def get_work_details(work_key: str) -> MatchResult | None:
"""Lädt Beschreibung und Genres nach."""
async with httpx.AsyncClient(timeout=12) as client:
try:
r = await client.get(f"{OL_BASE}{work_key}.json")
r.raise_for_status()
data = r.json()
except Exception:
return None
desc = data.get("description")
if isinstance(desc, dict):
desc = desc.get("value")
return MatchResult(
source="open_library",
source_id=work_key,
title=data.get("title", ""),
description=desc,
confidence=1.0,
)