Restructure into backend/ and frontend/ subprojects

- backend/ uses proper Python src layout (src/media_library_viewer_api/)
  with pyproject.toml, hatchling build, and PYTHONPATH=src convention
- frontend/ is a Vite + React + TypeScript SPA
- archive/ preserves the original Streamlit prototype for reference
- Cleaned up root to only contain docs, license, and subproject dirs
- Updated README for the new dual-subproject architecture
This commit is contained in:
2026-04-30 21:48:46 +02:00
parent 3c432473e5
commit 51b10438a9
47 changed files with 127 additions and 130 deletions
+49 -97
View File
@@ -1,148 +1,100 @@
# 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.
Media library management tool with Jellyfin integration, SSH file inspection, server monitoring, and safe remote job templates.
See `docs/REQUIREMENTS.md` for the living requirements, decisions, and planning history.
See `docs/MIGRATION_PLAN.md` for the FastAPI + React architecture plan.
Project policy/docs:
- License: `LICENSE` (MIT)
- Contributing guide: `CONTRIBUTING.md`
## Phase 1 features
## Architecture
- 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
- Server monitoring dashboard plus detailed Monitoring tab for CPU, IO wait, RAM, network, disk I/O, and disk space
- Jellyfin API-key connection using `GET /Users` plus user-scoped library endpoints
- 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
The project consists of two subprojects:
## Project structure
- **`backend/`** — FastAPI Python API (see `backend/README.md`)
- **`frontend/`** — React + TypeScript SPA (see `frontend/README.md`)
- **`archive/`** — Original Streamlit prototype (preserved for reference)
```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/
```
## Features
## Setup
- Dashboard with now-playing sessions, server monitoring overview, and per-library media counts
- Server monitoring with CPU, IO wait, RAM, network, and disk I/O charts
- SQLite-indexed media table with full-library sort/filter
- Remote file browser with ffprobe preview and job execution
- Jellyfin API integration for library metadata
- SSH-based file inspection and remote job templates
## Quick Start
### Backend
```bash
cd backend
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pip install -e '.[dev]'
uvicorn media_library_viewer_api.main:app --reload --port 8000
```
For development tools:
### Frontend
```bash
pip install -e '.[dev]'
cd frontend
npm install
npm run dev
```
Create `.env` or use Streamlit secrets/env vars:
Frontend runs on http://localhost:5173 and proxies API requests to http://localhost:8000.
## Configuration
Create a `.env` file in the project root:
```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
SSH_PASSWORD=
REMOTE_MEDIA_ROOT=/srv/media
REMOTE_PATH_PREFIX=
```
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:
The remote server needs:
```bash
ffprobe -version
python3 --version
find --version
stat --version
df --version
awk --version || true
```
- Linux `/proc` and `/sys/block` for monitoring
- `/bin/sh` (POSIX shell)
- `python3`, `ffprobe`, `find`, `stat`, `df`, `awk`
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:
The SSH client rejects unknown host keys. Connect manually once 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
## Development
```bash
PYTHONPATH=src python -m py_compile app.py src/media_library_viewer/*.py src/media_library_viewer/clients/*.py
ruff check .
pytest
# Backend
cd backend && PYTHONPATH=src python -m py_compile src/media_library_viewer_api/main.py
# Frontend
cd frontend && npx tsc --noEmit && npm run build
```
## 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.
- Jellyfin server root URL required (not `/web`). The client strips trailing `/web` defensively.
- SSH commands run through `/bin/sh -c` regardless of remote login shell.
- Job templates are shell-quoted. Add new templates in `backend/src/media_library_viewer_api/jobs.py`.
- Monitoring collector uses JSONL in `/tmp`, pruned to 7 days / 70k lines.