diff --git a/README.md b/README.md index 9710cf7..c4904e1 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Netflix style browsing of your local media archive. Supports keyboard, mouse and - Scans your chosen media folder for all movies and series that can be found - Produces preview video clips and downloads metadata - Search on names and other metadata, not just titles +- Remembers per-episode playback positions and offers series continue points - Hand off playback to your preferred system player Windows and macOS builds are currently portable-only (no installer). On first startup, the app asks for your media folder, which you can later change using the in-app folder icon. @@ -29,7 +30,7 @@ MediaHive is designed to work with a mouse, keyboard, or gamepad. | Input | Controls | | --- | --- | | Mouse | Click posters, rows, search, play, and folder actions directly. | -| Keyboard | Arrow keys move focus, `Enter` activates the focused item, `Escape` goes back, and `/` jumps to search. | +| Keyboard | Arrow keys move focus, `Enter` activates the focused item, `Escape` goes back, and `/` or `Ctrl`/`Cmd`+`F` jumps to search. | | Gamepad | D-pad or left stick moves focus, `A` selects or plays, and `B` goes back. `RB`/`LB` browses adjacent items, and the Search bar has an OSD keyboard. Player controls during playback. | ## Recommended Players diff --git a/docs/API.md b/docs/API.md index 963acbb..121d46c 100644 --- a/docs/API.md +++ b/docs/API.md @@ -10,26 +10,39 @@ All media paths are scoped to a **root**, identified by a friendly `root_id` | --- | --- | --- | | `GET` | `/api/health` | Lightweight health check. | | `GET` | `/api/config` | Returns the current root configuration. | -| `PUT` | `/api/config/roots` | Atomically replace the full root set. | -| `POST` | `/api/play/{root_id}` | Opens a media file with the system player. Also starts an assumed-playback session (see notes). | +| `PUT` | `/api/config/roots` | Atomically replace the full root set. Returns `{ "status": "ok", "accepted": [{path, root_id}], "failed": [...] }`. | +| `POST` | `/api/play/{root_id}` | Opens a media file with a media player. Also starts an assumed-playback session (see notes). | +| `GET` | `/api/players` | Lists detected media players. Returns `{ "players": [{id, name, family, path}] }`, including synthetic `default` and `custom` entries. | | `POST` | `/api/activity` | Reports user input activity; finalizes any assumed-playback session. Returns `{ "status": "ok", "finalized": bool }`. | -| `POST` | `/api/open-folder/{root_id}` | Opens a folder in the system file explorer. | -| `GET` | `/api/meta/{root_id}/{meta_key}` | Returns allowed metadata from `/.mediahive`. | -| `GET` | `/api/meta/playback-state` | Returns merged resume positions across all roots. Series entries carry one continue point per series (`season`/`episode` = last watched) plus a per-episode watch map (`episodes`: `"SE"` → `{pos, ts, done}`); completing an episode marks it done and advances the point to the next episode. | -| `POST` | `/api/meta/playback-state` | Updates one resume entry (`root_id`, `file_path`, `pos`; null `pos` clears a movie or advances a series' continue point). | -| `GET` | `/api/player/status` | Returns whether remote player control is currently available. | -| `GET` | `/api/mpcbe/status` | Reports whether MPC-BE's local web interface is reachable. | +| `POST` | `/api/open-folder/{root_id}` | Opens a folder in the system file explorer; given a file path, selects the file instead. | +| `GET` | `/api/meta/{root_id}/{meta_key}` | Returns allowed metadata from `/.mediahive`, as `{ "key": meta_key, "data": ... }`. | +| `GET` | `/api/meta/playback-state` | Returns merged resume positions across all roots, as `{ "key": "playback-state", "data": ... }`. Series entries carry one continue point per series (`season`/`episode` = last watched) plus a per-episode watch map (`episodes`: `"SE"` → `{pos, ts, done}`); completing an episode marks it done and advances the point to the next episode. | +| `POST` | `/api/meta/playback-state` | Updates one resume entry (`root_id`, `file_path`, `pos`; null `pos` clears a movie or advances a series' continue point). Returns `{ "status": "ok", "slug", "pos" }` plus `season`/`episode` when the continue point advances. | +| `GET` | `/api/player/status` | Returns whether remote player control is currently available. Accepts an optional `?port=` override (default 13579). | +| `GET` | `/api/mpcbe/status` | Reports whether MPC-BE's local web interface is reachable, as `{ "reachable": true|false }`. Accepts an optional `?port=` override; always `false` on non-Windows. | | `GET` | `/api/media/{root_id}/{file_path:path}` | Serves files from the specified root. | | `GET` | `/api/assets/{root_id}/{asset_type}/{asset_path:path}` | Serves typed assets from `/.mediahive`. | -| `WS` | `/api/ws` | Streams roots, index updates, and task progress for all roots. | +| `WS` | `/api/ws` | Streams roots, index updates, and task progress for all roots (see WebSocket notes). | ## Notes - `PUT /api/config/roots` accepts `{ "roots": { "name": "/absolute/path", ... } }`, validates paths, and atomically swaps the active set. -- `POST /api/play/{root_id}` and `POST /api/open-folder/{root_id}` expect JSON request bodies with `file_path` / `folder_path` relative to the root. -- `GET /api/media/{root_id}/{file_path:path}` is constrained to the specified root; path traversal outside the root is rejected. +- `POST /api/play/{root_id}` and `POST /api/open-folder/{root_id}` expect JSON request bodies with `file_path` / `folder_path` relative to the root. The play body additionally accepts `player_id` (a value from `GET /api/players`; unknown ids yield 400) and `player_custom_cmd` (command template used when `player_id` is `custom`). +- `GET /api/media/{root_id}/{file_path:path}` is constrained to the specified root; path traversal outside the root is rejected. Single-range requests are supported (`206` with `Content-Range`, `416` on invalid ranges), responses carry a weak `ETag` (`If-None-Match` yields `304`) and `Cache-Control: public, max-age=604800, immutable`. - `GET /api/assets/{root_id}/{asset_type}/{asset_path:path}` is constrained to `/.mediahive/{asset_type}` where `asset_type` is one of `movies`, `series`, `people`. - `GET /api/meta/{root_id}/{meta_key}` supports metadata keys currently limited to `playback-state` and `scanignore`. - `GET /api/player/status` returns `{ "remote": true|false }`. -- `GET /api/mpcbe/status` returns `false` on non-Windows platforms. +- Roots may also be provided at startup via the `MEDIAHIVE_ROOTS` environment variable (JSON dict of name → path), which overrides the persisted configuration. - Assumed playback: after `POST /api/play/{root_id}` the launched item is assumed to be playing while the frontend reports no input activity. On the next `POST /api/activity` the guessed position (`resume base + elapsed`, capped at the TMDb runtime) is written once; watches under 5 minutes are discarded (a peek is not progress). A resume entry written by another tracker (e.g. the GUI's MPC-BE tracker) during the session overrides the guess. The MPC-BE tracker likewise ignores sessions shorter than 5 minutes. + +## WebSocket + +`GET /api/ws` sends tagged msgspec JSON messages as binary frames (message shapes are defined in `mediahive/models/protocol.py`): + +- `roots` — full root list and per-root status: `{roots: [{root_id, path, status, error, snapshot_loaded, movies, series}]}`. +- `init` — full index payload `{roots: {root_id: {movies, series, people}}}`, re-sent when the root set changes or a snapshot finishes loading. +- `upsert` — single item inserted or updated: `{root_id, kind ("movie"|"series"), id, item, people?}`. +- `remove` — single item removed: `{root_id, kind, id}`. +- `task` — background task progress: `{root_id, data}`. + +Clients must send (any) text frame to keep the receive loop alive. diff --git a/docs/development.md b/docs/development.md index 33ab81a..8878e54 100644 --- a/docs/development.md +++ b/docs/development.md @@ -40,13 +40,14 @@ uv run --extra gui python -m mediahive.winmain /path/to/media/folder This launches the same pywebview-based desktop flow used by the Windows build. -## Migrate Existing Index Snapshots +## Building And Releasing -```bash -uv run python scripts/indexmigr.py /path/to/media/root --write -``` +The helper scripts are directly executable via their `uv run` shebang (on Windows, run them with `uv run scripts/.py`): -This applies versioned snapshot migrations to `.mediahive/index.json` outside the main application. Use it before starting a newer build against an older index. +- `./scripts/guibuild.py` builds the PyInstaller desktop app and a versioned portable ZIP under `build/`. +- `./scripts/release.py` publishes a release to the Gitea releases page. + +Python packaging builds the frontend automatically through the hatch build hook `scripts/fastapi-vue/buildhook.py` (see `pyproject.toml`), so wheels and sdists always ship a fresh `mediahive/frontend-build`. ## Notes @@ -54,3 +55,4 @@ This applies versioned snapshot migrations to `.mediahive/index.json` outside th - The desktop app remembers the chosen folder between launches. - HTTP and WebSocket endpoints are documented in [API.md](API.md). - MPC-BE integration details (Windows only) live in [mpc-be.md](mpc-be.md). +- Scanner/indexer design notes and the v0.5.0 rescan fixes are reviewed in [scanning-review.md](scanning-review.md). diff --git a/docs/mpc-be.md b/docs/mpc-be.md index 4b5b791..432e820 100644 --- a/docs/mpc-be.md +++ b/docs/mpc-be.md @@ -611,7 +611,9 @@ Current native command usage is centered on: - `889` for play/pause - `816` for exit -- `-1&position=HH:MM:SS` for exact 4-second seeking +- `-1&position=HH:MM:SS` to seek to the stored resume position when playback starts + +The GUI also polls `/variables.html` for the live position and duration and posts resume positions back to the MediaHive backend (`/api/meta/playback-state`), which is how per-episode resume positions and series continue points are tracked. Sessions shorter than 5 minutes are ignored. ## Guidance