Bugs fixed: - Partial rescans no longer replace whole index entries: Upsert events now carry the scanned torrent paths and IndexStore merges partial rebuilds, so touching one season no longer drops the others from listings. - Deleted torrents are now detected: discovery reports the full candidate set via a new Sync event after each completed scan, and the store prunes file entries/episodes/seasons/items whose torrents vanished. - Seen mtimes are committed only after a scan completes successfully, so cancelled/failed scans retry their items. - Showreel worker no longer rebroadcasts stale whole items; it sends narrow MovieShowreel/EpisodeReel events that update only reel fields. - Permanently failing reel generations (e.g. DoVi/libplacebo on GPU-less machines) and short-video re-queueing are no longer retried every scan: reel outcomes persist in reel-state.json with exponential backoff. Optimizations: - Persist scan state (scan-state.json) and ffmpeg probe results (probe-cache.json, keyed by mtime+size, failures included) under .mediahive/, written only when changed. A warm restart over an unchanged library drops from ~57 s + 467 queued reel tasks to ~0.2 s with an empty queue (measured on a 163-item root). - Bounded parallelism (semaphores + gather) for cast-profile downloads, TMDb title lookups, and season-detail fetches. - Incremental TMDb-id index bookkeeping instead of rebuilding per upsert; removed dead trigger_scan. See docs/scanning-review.md for the full findings/fixes/measurements report.
77 lines
2.0 KiB
Python
77 lines
2.0 KiB
Python
"""Event types shared between scanner and WebSocket.
|
|
|
|
These types are used as:
|
|
- Internal scan events (scanner → server queue)
|
|
- WebSocket messages (server → clients)
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import msgspec
|
|
|
|
from .data import Movie, Series, TaskInfo
|
|
from .tmdb import Person
|
|
|
|
|
|
class Upsert(msgspec.Struct, tag="upsert"):
|
|
"""Single item inserted or updated.
|
|
|
|
``scanned`` lists the media-root-relative torrent paths whose content was
|
|
(re)scanned to build this item. When present, the store merges the item
|
|
into the existing entry instead of replacing it wholesale: only data
|
|
belonging to the scanned torrents is replaced. ``None`` means full
|
|
replacement (legacy behaviour).
|
|
"""
|
|
|
|
kind: str # "movie" or "series"
|
|
id: str
|
|
item: Movie | Series
|
|
people: dict[int, Person] | None = None
|
|
scanned: list[str] | None = None
|
|
|
|
|
|
class Remove(msgspec.Struct, tag="remove"):
|
|
"""Single item removed."""
|
|
|
|
kind: str
|
|
id: str
|
|
|
|
|
|
class Sync(msgspec.Struct, tag="sync"):
|
|
"""Full set of media-root-relative torrent paths currently on disk.
|
|
|
|
Sent by the scanner after a successfully completed discovery pass so the
|
|
store can drop entries whose files no longer exist. Internal only —
|
|
never forwarded to WebSocket clients.
|
|
"""
|
|
|
|
paths: list[str]
|
|
|
|
|
|
class MovieShowreel(msgspec.Struct, tag="movie-showreel"):
|
|
"""Reel worker result for a movie (internal, scanner → store)."""
|
|
|
|
id: str
|
|
showreel_images: list[str] | None = None
|
|
showreel_source_sets: list[list[str]] | None = None
|
|
|
|
|
|
class EpisodeReel(msgspec.Struct, tag="episode-reel"):
|
|
"""Reel worker result for one episode (internal, scanner → store)."""
|
|
|
|
id: str
|
|
season: int
|
|
episode: int
|
|
reel_image: str | None = None
|
|
reel_sources: list[str] | None = None
|
|
|
|
|
|
class Task(msgspec.Struct, tag="task"):
|
|
"""Task progress broadcast."""
|
|
|
|
data: TaskInfo
|
|
|
|
|
|
# Union of scan events (scanner → server) and WS broadcast messages
|
|
ScanEvent = Upsert | Sync | MovieShowreel | EpisodeReel | Task
|