# Media Library Viewer Small Streamlit app for browsing a remote Jellyfin library, inspecting files on disk over SSH, previewing media metadata with `ffprobe`, and running safe remote job templates. See `docs/REQUIREMENTS.md` for the living requirements, decisions, and planning history. Project policy/docs: - License: `LICENSE` (MIT) - Contributing guide: `CONTRIBUTING.md` ## Phase 1 features - Dashboard media counts for movies, series, and episodes, plus now-playing sessions (user, title, playback state, transcoding) - SQLite-indexed Media tab with full-library sort/filter for runtime, size, bitrate, explicit HDR yes/no flag, date added, codec, resolution, series, season, episode, path, and row-based selection that automatically syncs File browser to the selected item's folder - SSH resource overview dashboard plus detailed Resources tab for CPU, RAM, network, disk I/O, and disk space - Jellyfin API-key connection using `GET /Users` plus user-scoped library endpoints - Library selection, search, pagination, poster grid - Item details with Jellyfin metadata and raw JSON - Compact SSH remote directory browser with clickable rows, search, filters, sorting, pagination, and `[UP] ..` navigation - Blocking selected-file `ffprobe` preview for known video files - Separate container, video, audio, and subtitle metadata sections - `stat` inspection - Safe/read-only job templates, designed to extend later ## Project structure ```text . ├── app.py # Thin Streamlit entrypoint ├── pyproject.toml # Package metadata, dependencies, tool config ├── requirements.txt # Convenience install file (-e .) ├── docs/ │ └── REQUIREMENTS.md # Living requirements and decision log ├── src/ │ └── media_library_viewer/ │ ├── app.py # Thin Streamlit orchestration layer │ ├── config.py # Environment/.env config │ ├── jobs.py # Remote job templates │ ├── utils.py # Formatting and ffprobe summaries │ ├── domain/ # UI-independent normalization/domain helpers │ ├── services/ # UI-independent application services/indexes │ ├── ui/ # Streamlit UI modules split by feature area │ └── clients/ │ ├── jellyfin.py # Jellyfin API client │ └── ssh.py # SSH/ffprobe client └── tests/ ``` ## Setup ```bash python -m venv .venv source .venv/bin/activate pip install -r requirements.txt ``` For development tools: ```bash pip install -e '.[dev]' ``` Create `.env` or use Streamlit secrets/env vars: ```bash JELLYFIN_URL=https://jellyfin.example.com JELLYFIN_API_KEY=your-api-key # Optional fallback if user selection via GET /Users does not work. JELLYFIN_USER_ID= SSH_HOST=media-server.example.com SSH_USERNAME=username SSH_PORT=22 SSH_KEY_FILENAME=/home/username/.ssh/id_rsa # SSH_PASSWORD=optional-password-or-key-passphrase REMOTE_MEDIA_ROOT=/mnt/media # Optional fallback prefix when REMOTE_MEDIA_ROOT mapping is not enough. # Example fallback: Jellyfin /media/... -> SSH /srv/media/... REMOTE_PATH_PREFIX=/srv ``` Run: ```bash streamlit run app.py ``` ## Before publishing / committing Keep these out of git: - `.env` and any real secrets/tokens/passwords - `.streamlit/secrets.toml` - `.venv/`, local IDE files (`.idea/`, `.vscode/`) - local cache/index files under `.cache/` - logs and temporary files The included `.gitignore` already excludes these. ## Remote server requirements For file browsing, resource metrics, and media metadata: ```bash ffprobe -version python3 --version find --version stat --version df --version awk --version || true ``` The resource dashboard uses Linux `/proc`, `/sys/block`, `/bin/sh`, and a lightweight collector started over SSH. No full monitoring stack is required, but last-hour charts require the collector to have been running long enough to gather samples. Network is shown as download/upload in bytes per second, and disk I/O is shown as read/write in bytes per second. The metrics JSONL file is pruned to 7 days with a 70,000-line safety cap. SSH commands are explicitly run through `/bin/sh -c`, so the remote user's login shell may be fish or another shell. If no samples appear after 10-20 seconds, use the dashboard's `Restart` button and inspect `Collector diagnostics`. The SSH client uses your local `known_hosts` and rejects unknown host keys. Connect once manually first: ```bash ssh user@host ``` ## Extending jobs Add templates in `src/media_library_viewer/jobs.py`: ```python JOB_TEMPLATES["my_job"] = JobTemplate( name="My job", description="What it does.", command_template="my-command --input {path}", destructive=False, ) ``` Template variables are shell-quoted before insertion. For destructive jobs, add confirmation UI before executing. ## Development checks ```bash PYTHONPATH=src python -m py_compile app.py src/media_library_viewer/*.py src/media_library_viewer/clients/*.py ruff check . pytest ``` ## Notes - Prefer Jellyfin API for library metadata. - Enter the Jellyfin server root URL, for example `https://jellyfin.example.com`, not `https://jellyfin.example.com/web`. The client strips a trailing `/web` defensively. - Prefer SSH/ffprobe for authoritative disk-level media metadata such as actual bitrate, color transfer, HDR metadata, audio channels, subtitles, and container details. - Keep cleanup/transcode jobs explicit and template-based; avoid free-form command execution in the UI unless this app is only used locally by trusted users.