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

.
├── 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

python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

For development tools:

pip install -e '.[dev]'

Create .env or use Streamlit secrets/env vars:

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:

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:

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:

ssh user@host

Extending jobs

Add templates in src/media_library_viewer/jobs.py:

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

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.
S
Description
No description provided
Readme MIT 5.7 MiB
Languages
Python 57.9%
TypeScript 40.4%
JavaScript 0.8%
Shell 0.5%
CSS 0.3%
Other 0.1%