Rewrite player + fix matching metadata loss
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>
This commit is contained in:
@@ -1,17 +1,19 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from ..dependencies import get_db, get_current_user, require_admin
|
||||
from ..models.user import User
|
||||
from ..models.media_item import LibraryItem
|
||||
from ..services.matcher import match_audiobook, search_for_item, _apply_match, _score_result
|
||||
from ..services.matcher import match_audiobook, search_for_item, _apply_match, _enrich_match
|
||||
from ..services.matching.musicbrainz import get_release_details
|
||||
from ..services.matching.open_library import get_work_details
|
||||
from ..services.matching.google_books import search_google_books
|
||||
from ..services.matching.base import MatchResult
|
||||
from datetime import datetime
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/items", tags=["matching"])
|
||||
|
||||
|
||||
@@ -59,7 +61,7 @@ async def apply_match(
|
||||
):
|
||||
"""
|
||||
Wendet einen manuell gewählten Match-Treffer an.
|
||||
body: { source, id, title, author, ... }
|
||||
body: { source, id, title, author, narrator, description, publisher, publishYear, series, seriesSequence, language, genres, cover, ... }
|
||||
"""
|
||||
result = await db.execute(select(LibraryItem).where(LibraryItem.id == item_id))
|
||||
item = result.scalar_one_or_none()
|
||||
@@ -69,27 +71,44 @@ async def apply_match(
|
||||
source = body.get("source", "manual")
|
||||
source_id = body.get("id", "")
|
||||
|
||||
# Versuche Details zu laden wenn MusicBrainz/OpenLibrary
|
||||
match_result = None
|
||||
if source == "musicbrainz":
|
||||
match_result = await get_release_details(source_id)
|
||||
elif source == "open_library":
|
||||
from ..services.matching.open_library import get_work_details
|
||||
match_result = await get_work_details(source_id)
|
||||
logger.info(
|
||||
f"Manual apply: item={item_id} source={source} source_id={source_id} "
|
||||
f"body_keys={sorted(body.keys())}"
|
||||
)
|
||||
|
||||
if not match_result:
|
||||
# Fallback: nur die übergebenen Daten verwenden
|
||||
match_result = MatchResult(
|
||||
source=source,
|
||||
source_id=source_id,
|
||||
title=body.get("title", item.title or ""),
|
||||
author=body.get("author"),
|
||||
publish_year=body.get("publishYear"),
|
||||
cover_url=body.get("cover"),
|
||||
confidence=1.0,
|
||||
)
|
||||
# Immer aus body konstruieren (search_for_item liefert jetzt alle Felder)
|
||||
match_result = MatchResult(
|
||||
source=source,
|
||||
source_id=source_id,
|
||||
title=body.get("title") or item.title or "",
|
||||
subtitle=body.get("subtitle"),
|
||||
author=body.get("author"),
|
||||
narrator=body.get("narrator"),
|
||||
description=body.get("description"),
|
||||
publisher=body.get("publisher"),
|
||||
publish_year=body.get("publishYear"),
|
||||
series=body.get("series"),
|
||||
series_sequence=body.get("seriesSequence"),
|
||||
language=body.get("language"),
|
||||
genres=body.get("genres") or [],
|
||||
cover_url=body.get("cover"),
|
||||
confidence=1.0,
|
||||
)
|
||||
|
||||
match_result.confidence = 1.0 # Manuell → immer akzeptieren
|
||||
# Mit Details anreichern (Beschreibung, Kapitel) — überschreibt keine vorhandenen Werte
|
||||
try:
|
||||
if source == "musicbrainz":
|
||||
details = await get_release_details(source_id)
|
||||
if details:
|
||||
_enrich_match(match_result, details)
|
||||
elif source == "open_library":
|
||||
details = await get_work_details(source_id)
|
||||
if details:
|
||||
_enrich_match(match_result, details)
|
||||
except Exception as e:
|
||||
logger.warning(f"Details-Laden fehlgeschlagen ({source}: {source_id}): {e}")
|
||||
|
||||
match_result.confidence = 1.0
|
||||
await _apply_match(db, item, match_result, confidence=1.0)
|
||||
item.match_locked = True
|
||||
item.updated_at = datetime.utcnow()
|
||||
|
||||
Reference in New Issue
Block a user