- Fix merged playback-state dropping season/episode, which broke series
resume restore entirely.
- Track watch progress per episode (episodes map keyed S..E.., with done
markers on completion) while keeping one continue point per series as
the last watched episode, used for spoiler protection and season
selection. MPC-BE tracker resumes from each episode's own position.
- Assumed-playback fallback for any player and server-only mode: a launch
starts a session and the guessed position (base + elapsed, capped at
runtime) is written when frontend activity resumes; the real MPC-BE
tracker overrides guesses via timestamps. Frontend reports input
activity (throttled) via POST /api/activity.
- Ignore watches under 5 minutes in both trackers.
- Episode tiles show a small quadrant-circle watch indicator; near end
counts as fully watched, no data shows nothing.
- Fix episode audio ownership: single audioOwnerKey shared by mouse hover
and keyboard/gamepad focus, no longer clobbered by playback sync;
idle fade can be re-armed by continued activity.
Replace the 3D ring jukebox with a flat curtain browser: selected season
faces the screen as top of the left stack, side cards rotate about their
outer edge and recede into the screen, each side evenly spaced with pitch
adapting to its card count so cards never leave the stage.
- Mount episode videos only after the season switch settles, then
preload=auto + autoplay (avoids mid-animation media setup jank)
- Start videos immediately (no stagger) once an episode is pointed at;
keep staggered starts while navigating the season selector
- Keyboard/gamepad entry into the season row lands on the selected
season via data-nav-entry-col instead of the x-closest card
- Black episode tiles; hidden (ahead-of-cursor) tiles get a sheen and
vignette veil that cross-fades on reveal/conceal
- Nudge season info panel 8px left for balanced gaps
Render only the selected season's episodes; switch via a perspective-arc
poster carousel with floating season info, deferred video loading with
still-image fade-in, and spoiler fade for episodes ahead of the cursor.
uvicorn's reloader is unusable on Windows for this app: it restarts the
server child via CTRL_C_EVENT, which is never delivered to a plain spawn
child (no console process group of its own), so the child kept running
old code and the reloader blocked in process.join() after the first
reload — the scanner included. And when a child does restart, uvicorn
hands it parent-bound sockets that ProactorEventLoop cannot register
with IOCP (WinError 87 on accept), while the selector loop would break
asyncio subprocesses (ffmpeg/ffprobe showreel generation).
In dev mode on Windows, mediahive now runs a small supervisor instead:
it watches the package with watchfiles and respawns a fresh child
process (which binds its own sockets) on every change, and terminates
the child on Ctrl-C. POSIX keeps uvicorn's native reload.
Shutdown: RootScanner.stop() no longer does a final best-effort state
save — those to_thread writes ran on the asyncio default executor, whose
non-daemon threads blocked interpreter exit, so log lines appeared after
the shell prompt returned. All scanner state is already persisted after
each completed scan and when the showreel queue drains. Shutdown-time
log lines (scan cancelled, showreel worker stopping) move to debug.
The directory worker registered video files with
'node["changed"] = node["changed"] or await _register_candidate(...)',
so once one file in a directory was found changed, the rest were never
registered: they fell out of known_paths, were pruned from the persisted
seen-mtimes at finalize, and were rediscovered as new on every scan.
That rotating ~10-item subset was reprocessed (metadata, TMDb lookups,
logs) every 30 seconds forever.
Also fix the self-sustaining reprocess loop for items that produce no
index entries (no playable file, no parseable episodes, non-media):
they never appear in the index, so the DB-aware mtime gating alone could
never skip them. They are now recorded in a persisted empty_mtimes map
(scan-state.json) and skipped while their mtime is unchanged.
Episode/playable/blu-ray finder caches are now cleared at the start of
each scan: an empty result cached before a download finished (or during
a transient network-mount error) previously stuck for the process
lifetime and reported "no episodes found" for series that have episodes.
Logging: a no-change scan now emits a single line with item count and
duration; per-item "up to date" chatter moved to debug; change scans
report the new/changed count plus per-item reason lines (stored vs
current mtime, index/empty-marker membership), finalize prune/commit
traces, and scan-state load/save confirmations.
Add AGENTS.md noting the project's Python 3.14 baseline (PEP 758
unparenthesized except) so tooling does not flag it as a syntax error.
Skip ensure-visible and synced-row animations on focus restore, and
snapshot/restore panel scrollTop plus synced-row scrollLeft around
detail navigation so the browse page comes back pixel-identical.
dir_state now records only subtrees with >= _MIN_TRACKED_DIRS (8)
directories that yielded no items of interest; hot or tiny trees fall
back to default every-pass scheduling with no bookkeeping. Per-directory
classification already works at any depth, so e.g. torrents/Music is
pruned at its own root while torrents/Movies stays hot; backed-off
subtrees are still re-examined when their (exponentially growing, 1h
cap) timer expires.
- RootScanner takes indexed_paths (store torrent keys); a candidate is
skipped only when its mtime is unchanged AND it is present in the index,
so an emptied/partial index self-heals on the next pass (non-indexable
OTHER candidates still skip on mtime alone)
- discovery: a directory that cannot be listed (transient SMB failure)
now carries its previously known paths into the Sync set instead of
dropping them, and an unreadable media root fails the whole scan —
previously both produced a partial/empty known_paths that made
sync_torrent_paths delete items that still exist on disk
- worker: broad per-directory exception guard (carry known paths, stay
hot, keep other workers running); node completion hardened against
double-decrement; backoff state not updated from cancelled walks
- scanner.stop: await cancelled tasks concurrently under one 2s budget
and persist scan/reel/probe state concurrently (was sequential)
- supervisor.shutdown: stop root contexts concurrently (per-root scanner
waits and network-mount snapshot flushes no longer add up)
- winmain: retry log-file rotation while a previous instance still holds
mediahive.log (Windows denies rename of an open file — the old instance
dying slowly made new instances crash with a PermissionError); after 2s
show a 'waiting for previous instance' window, retry up to 15s, then
fall back to a per-pid log file
- winmain: gui_main() wraps winmain() and renders unhandled exceptions as
a TraceRite HTML page in a pywebview window instead of PyInstaller's
plain-text fatal-error dialog; __main__ GUI mode uses it too
- scanner: replace serial DFS with 16-worker priority work-queue, one
scandir round trip per directory (DirEntry mtimes), per-subtree
exponential backoff (persisted in scan-state.json v2), leaf candidates
gated on max(dir, newest video file) mtime so growing downloads and
lazy NTFS dir mtimes are handled
- spec: collect_data_files('tracerite') so style.css/script.js ship
- guibuild: tolerate non-UTF-8 console when printing status glyphs
- ruff format pass on indexer.py/index_store.py (hook requirement)
The wheel hook was a local addition (76cd022) that rebuilt the frontend
on every wheel build. Only the sdist build should invoke node; wheel
builds (including wheel-from-sdist) package the prebuilt frontend-build.
Restores scripts/fastapi-vue/buildhook.py to its pristine upstream state.
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.
- Observe the video element itself instead of el.closest('.episode-tile'):
ref callbacks can fire before ancestors are attached, so no tile was
ever observed and the visibility allowlist blocked all playback.
- Use a blocklist for off-screen culling (play until reported off-screen)
so observer lag/failure degrades to the old always-on behavior.
- Default to the first season playing on page view again.
- Keep preload="auto" so tiles show a first-frame preview picture;
playback gating already prevents GPU use from inactive videos.
- Stop videos staggered (same 500ms step as startup) on idle stop,
season switch and scroll culling; same for movie page showreels.
- Add useIdlePreviewPlayback composable: stops preview videos after 30s
without input and restarts them staggered on activity; also stops
while the tab is hidden.
- SeriesFullView: no episode videos play until a season is browsed
(keyboard/gamepad focus or mouse hover); only near-viewport tiles of
the active season play (IntersectionObserver); inactive seasons no
longer preload video data; sync is incremental so playing tiles are
not rewound on scroll.
- MediaDetail: header showreel videos use cancellable stagger timers,
pause when scrolled out of view, and honor the idle stop.
- Images: lazy/async decoding on series page posters; content-visibility:
auto on media cards to skip rendering off-screen row items.