Commit Graph
204 Commits
Author SHA1 Message Date
LeoVasanko 59d2f157eb Probe scoop dotnet-sdk path explicitly (runner service has stale env)
release / gui-build (linux, bash) (push) Successful in 1m3s
release / gui-build (macos, bash) (push) Successful in 1m5s
release / gui-build (windows, cmd) (push) Successful in 1m34s
2026-09-22 23:14:08 +00:00
LeoVasanko 3377702e98 Build Windows installer with Velopack instead of WiX
release / gui-build (linux, bash) (push) Successful in 1m13s
release / gui-build (windows, cmd) (push) Failing after 1m34s
release / gui-build (macos, bash) (push) Successful in 1m13s
vpk runs on the machine's modern .NET runtime (WiX 3.11's .NET Framework
shim fails under the SYSTEM account) and produces a per-user Setup.exe
that needs no runtime on end-user machines.
2026-09-22 23:11:30 +00:00
LeoVasanko 6dee65b4c6 Remove temporary Windows host probe workflow 2026-09-22 22:40:37 +00:00
LeoVasanko c59040ce52 Surface WiX tool stdout/stderr on MSI build failure
release / gui-build (linux, bash) (push) Successful in 1m11s
release / gui-build (windows, cmd) (push) Failing after 1m31s
release / gui-build (macos, bash) (push) Failing after 11m44s
2026-09-22 22:38:14 +00:00
LeoVasanko 734b7993a2 Add per-user MSI installer for Windows; strip Mark-of-the-Web in frozen ZIP builds
release / gui-build (linux, bash) (push) Successful in 1m12s
release / gui-build (macos, bash) (push) Successful in 1m14s
release / gui-build (windows, cmd) (push) Failing after 1m31s
Files extracted from a downloaded ZIP carry a Zone.Identifier stream and the
.NET Framework CLR refuses to load such assemblies, so pythonnet failed with
'Failed to resolve Python.Runtime.Loader.Initialize'. MSI-installed files have
no MOTW; the winmain strip fixes the ZIP distribution.
2026-09-22 22:28:16 +00:00
LeoVasanko b01e8a5c3d Exclude Python.Runtime.dll from UPX (packing corrupts CLR metadata)
release / gui-build (macos, bash) (push) Successful in 1m14s
release / gui-build (linux, bash) (push) Successful in 1m13s
release / gui-build (windows, cmd) (push) Successful in 1m21s
2026-09-22 22:00:57 +00:00
LeoVasanko b231f450bf Temporary Windows host probe workflow
release / gui-build (linux, bash) (push) Successful in 1m2s
release / gui-build (macos, bash) (push) Successful in 1m2s
release / gui-build (windows, cmd) (push) Successful in 1m42s
2026-09-22 04:59:53 +00:00
LeoVasanko 67489e05bc Use cmd shell for Windows steps; powershell is blocked by execution policy under SYSTEM
release / gui-build (windows, cmd) (push) Failing after 0s
release / gui-build (macos, bash) (push) Successful in 1m0s
release / gui-build (linux, bash) (push) Successful in 1m2s
2026-09-22 04:26:30 +00:00
LeoVasanko 3ae4ce9f9f Fix Windows checkout (powershell, not WSL bash) and install node per-job via uv
release / gui-build (windows) (push) Failing after 0s
release / gui-build (macos) (push) Successful in 1m12s
release / gui-build (linux) (push) Successful in 1m15s
2026-09-22 04:23:48 +00:00
LeoVasanko 99e757bc6f Checkout with plain git clone; JS actions need node on host runners
release / gui-build (windows) (push) Failing after 1s
release / gui-build (macos) (push) Successful in 1m8s
release / gui-build (linux) (push) Successful in 1m11s
2026-09-22 04:13:21 +00:00
LeoVasanko 5300fd0c9c Bundle PyQtWebEngine on macOS and ship the app as a DMG
release / gui-build (linux) (push) Successful in 1m13s
release / gui-build (macos) (push) Successful in 1m15s
release / gui-build (windows) (push) Failing after 1s
pywebview[qt] no longer depends on PyQtWebEngine, so fresh CI builds
produced a macOS app without its Chromium engine that crashed on launch.
Package MediaHive.app as a compressed DMG instead of zipping the raw
onedir folder; release.py accepts .dmg artifacts.
2026-09-22 04:07:30 +00:00
LeoVasanko ff10c86e84 Add Gitea Actions cross-platform release workflow
Build the GUI app on macos/windows/linux host runners on v* tag pushes.
All platform jobs converge on one Gitea release; release.py now reuses an
existing release, skips duplicate assets, and supports --no-dist so only
the linux job uploads the wheel/sdist.
2026-09-22 03:54:05 +00:00
LeoVasanko c11cbd4250 Fix console encoding crash in release script summary output 2026-09-10 21:33:43 +00:00
LeoVasanko 9779857dcd Update documentation for the v0.5.0 release v0.5.0 2026-09-10 21:30:24 +00:00
LeoVasanko 9ec4f877eb Resume tracking: per-episode positions, player-agnostic fallback, series continue point
- 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.
2026-09-10 20:56:03 +00:00
LeoVasanko 6a6a012efe Series detail: curtain poster browser for seasons
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
2026-09-10 16:47:33 +00:00
LeoVasanko 2c28ab1f25 Series detail: one season at a time with 3D jukebox season selector
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.
2026-09-10 03:34:52 +00:00
LeoVasanko e6eadb2ecd Working dev reload on Windows; fast quiet scanner shutdown
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.
2026-09-09 15:15:27 +00:00
LeoVasanko ff6b195973 Fix rescan loop: short-circuited registration dropped files from state
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.
2026-09-09 14:40:32 +00:00
LeoVasanko 36e2fdd5ff Preserve browse scroll positions exactly when returning from details
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.
2026-09-09 04:51:10 +00:00
LeoVasanko 8f462b9e1d Track backoff state only for large empty subtrees
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.
2026-09-09 04:27:15 +00:00
LeoVasanko 838db5b55c Make rescans robust: DB-aware mtime gating, wipe-proof discovery
- 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
2026-09-09 04:21:17 +00:00
LeoVasanko e5300eaac0 Faster GUI exit, graceful second-instance startup, pretty crash window
- 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
2026-09-09 03:46:27 +00:00
LeoVasanko d1f1b9ecb8 Parallel adaptive discovery walk; fix PyInstaller tracerite data files
- 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)
2026-09-09 03:37:31 +00:00
LeoVasanko 26af7c633b Remove rtorrent scripting from the repository. 2026-09-09 02:14:11 +00:00
LeoVasanko c072f15cb5 Make helper scripts directly executable with uv run shebang
guibuild.py and release.py use the project environment; rtorrent-manager.py
is standalone and declares its own dependency via uv script metadata.
2026-09-09 02:08:49 +00:00
LeoVasanko 23030cd1c4 Remove wheel build hook, following upstream fastapi-vue convention
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.
2026-09-09 02:02:25 +00:00
LeoVasanko 2a39e1f0ea Fix scanner rescan bugs and wasted work
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.
2026-09-04 00:00:53 +00:00
LeoVasanko d3addadf14 Work around Starlette StreamingResponse bug with not aclosing the async generator by implementing a custom StreamingFileResponse class. 2026-09-03 13:20:18 +00:00
LeoVasanko 0a4d54c1b7 Update GUI logging to fastapi-vue too. 2026-09-03 12:30:53 +00:00
LeoVasanko c2776d2e2d fastapi-vue-setup 1.4.1 upgrade, replaces our own access logging and more. 2026-09-03 12:19:50 +00:00
LeoVasanko e5bc736ffa Fix episode preview playback and add staggered stopping
- 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.
2026-09-03 03:19:35 +00:00
LeoVasanko 258fc79753 Reduce preview video GPU load: idle stop, browse-gated seasons, viewport culling
- 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.
2026-09-03 02:23:22 +00:00
LeoVasanko 7a60ef5384 Implement Ctrl-R/F5 reload handler with intent to allow reloads in GUI build. 2026-09-02 19:42:17 +00:00
LeoVasanko 22454f2d29 Update README v0.4.3 2026-06-03 00:41:52 +00:00
LeoVasanko 589c789d4d Fix pywebview qt dep on non-Windows platforms. 2026-06-03 00:33:37 +00:00
LeoVasanko 5f2454d8e8 Simpler and cross-platform added_on timestamp determination. 2026-06-03 00:01:06 +00:00
LeoVasanko 2fa15132fb Make default script run with GUI if possible. Add --gui to force GUI. 2026-06-02 23:11:08 +00:00
LeoVasanko 76cd0224de Attempt build fix to allow running from git. 2026-06-02 22:45:09 +00:00
LeoVasanko b0d13a67a0 Fix config saving by not trying to save None default values. 2026-06-02 22:35:44 +00:00
LeoVasanko 568605b09a Add missing file from previous commit v0.4.2 2026-06-02 22:22:49 +00:00
LeoVasanko b09118c8cb Clean up series nested context menus and implement keyboard/gamepad navigation for these functions. v0.4.1 2026-06-02 22:17:47 +00:00
LeoVasanko f79c044250 Tighten media cache headers 2026-05-31 02:45:16 +00:00
LeoVasanko a4b0cd916a Use image logo for HDR10+ badge 2026-05-31 02:31:35 +00:00
LeoVasanko e1a961d21f Detect and badge HDR10+ (SMPTE ST 2094-40) via ffmpeg showinfo 2026-05-31 02:05:10 +00:00
LeoVasanko 1a6a3f0cf2 Don't show collection where the current movie is the only item we have 2026-05-31 01:51:15 +00:00
LeoVasanko 626fb9a0ae Unify playback-state read/write via /api/meta/playback-state 2026-05-31 01:43:56 +00:00
LeoVasanko b6742f0f27 refactor: unify roots updates over single websocket 2026-05-31 00:26:51 +00:00
LeoVasanko 6d0e6d41a7 feat(media): use collection-based recommendations and refine poster display 2026-05-31 00:12:54 +00:00
LeoVasanko 932c5af1ff fix(frontend): scope synced row scrolling by group 2026-05-30 23:54:11 +00:00