Sourced from Figma's "53 Unique Website Color Schemes" resource page, which only shows rendered mockup screenshots (no raw hex data) - extracted dominant colors via canvas pixel sampling, then a small pipeline (tools/generate_website_scheme_themes.py) maps each scheme's real colors into this app's palette shape, with contrast enforced against both the background and the white text every .btn uses. Cut 11 of the original 53 after review: 2 for a neon-bright accent (the saturation cap only bounded lightness at first, so ~28 of 53 accents landed at pure 100% saturation - fixed at the pipeline level, calibrated against what the existing hand-picked themes actually use), and 9 for being near-duplicates of another kept scheme once mapped into actual UI colors (measured by real color distance, not eyeballed) - including one pixel-identical pair and a 4-way cluster trimmed to its two most distinct members. Settings dropdown is grouped into <optgroup>s (Base, Editor Themes, and 5 categories matching the source page's own curation) rather than one flat 54-option list. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
209 lines
8.7 KiB
Markdown
209 lines
8.7 KiB
Markdown
# OfflineU
|
|
|
|
Self-hosted course viewer for a local video/audio/text training library. Runs
|
|
as a single Flask app in Docker, tracks watch progress and notes per lesson,
|
|
and gives the whole library a dashboard-style home page instead of a bare
|
|
file browser.
|
|
|
|
This is a personal deployment, not a public project — this doc is internal
|
|
reference for running/maintaining it, not a pitch.
|
|
|
|

|
|
*Screenshot predates the current theming/icon work — kept for a rough idea
|
|
of layout, not pixel-accurate.*
|
|
|
|
---
|
|
|
|
## Deployment
|
|
|
|
Deployed via `docker-compose.yml`, built directly from the `Dockerfile` in
|
|
this repo (`pull_policy: build`, not a registry pull):
|
|
|
|
```yaml
|
|
services:
|
|
offlineu:
|
|
build: .
|
|
pull_policy: build
|
|
container_name: offlineu
|
|
network_mode: host
|
|
ports:
|
|
- "5000:5000"
|
|
environment:
|
|
- PUID=1000
|
|
- PGID=10
|
|
- TZ=America/New_York
|
|
- FLASK_ENV=production
|
|
volumes:
|
|
- /volume1/files/training:/app/courses # course library
|
|
- /volume2/docker/offlineu/data:/app/data # settings/progress/notes
|
|
restart: unless-stopped
|
|
```
|
|
|
|
**Auto-deploy:** the Gitea repo has a webhook to Dockhand — a push to `main`
|
|
triggers a full image **rebuild** from the Dockerfile (confirmed, not just a
|
|
container restart), so Dockerfile changes (e.g. adding `ffmpeg`) take effect
|
|
on the very next push without any manual step.
|
|
|
|
---
|
|
|
|
## Local development
|
|
|
|
```bash
|
|
pip install -r requirements.txt
|
|
python offlineu_core.py --library-path /path/to/courses --debug
|
|
```
|
|
|
|
Opens on `http://127.0.0.1:5000`. `ffprobe` (from ffmpeg) needs to be on
|
|
`PATH` for video-duration lookups to work locally; without it those just
|
|
silently stay blank instead of erroring.
|
|
|
|
### CLI options
|
|
|
|
| Option | Description |
|
|
| ---------------------- | ----------------------------------------------------- |
|
|
| `--host` | Bind host (default `0.0.0.0`) |
|
|
| `--port` | Bind port (default `5000`) |
|
|
| `--debug` | Enable Flask debug mode |
|
|
| `--create-templates` | Regenerate default templates if missing |
|
|
| `--library-path` | Course library root (overrides `COURSES_LIBRARY_PATH`) |
|
|
| `course_path` (positional) | Load a specific course directly at startup |
|
|
|
|
### Environment variables
|
|
|
|
| Variable | Default | Purpose |
|
|
| ---------------------- | -------------- | ------------------------------------------ |
|
|
| `COURSES_LIBRARY_PATH` | `/app/courses` | Course library root |
|
|
| `OFFLINEU_DATA_DIR` | `/app/data` | Where settings/progress/notes data lives |
|
|
| `AUTO_LOAD_COURSE` | — | Course path to load automatically on start |
|
|
|
|
---
|
|
|
|
## Features
|
|
|
|
**Library browsing**
|
|
- Lazy-loading folder browser (grid or list view) with search, including an
|
|
opt-in transcript search across subtitle files
|
|
- Course cards show media file count, total video/audio runtime, and
|
|
completion % at a glance
|
|
- Cover art: uses a manually-placed `cover`/`folder`/`thumbnail`/`thumb`/
|
|
`poster` image if a course has one, otherwise auto-generates one via
|
|
`ffmpeg` from a frame of the course's first video
|
|
- Hide courses/folders from the browser without touching anything on disk;
|
|
bulk-select to hide or queue several at once
|
|
- Bulk rename across course/folder names (find & replace)
|
|
|
|
**Dashboard**
|
|
- Library-wide stats (courses, lessons completed, time watched, time
|
|
remaining, day streak) and a 90-day activity heatmap
|
|
- Next Up queue (manually curated, reorderable)
|
|
- Pick Back Up (courses with progress that have gone stale)
|
|
- Recently Added / Recently Viewed
|
|
- Surprise Me — dice icon in the header, random pick weighted toward
|
|
incomplete courses
|
|
|
|
**Playback & progress**
|
|
- Video/audio player with resize, playback-speed presets, and resume-from-
|
|
last-position
|
|
- Auto-tracks watch progress and completion per lesson
|
|
- Video/audio durations read via `ffprobe` and cached persistently per
|
|
course (`.offlineu_duration_cache.json`), so total runtime and per-lesson
|
|
length show up before you've ever pressed play — not just after; the
|
|
same cache backs the library-wide "time remaining" stat
|
|
- Settings → "Precompute Lengths & Cover Art" walks the whole library in
|
|
the background to populate durations and thumbnails up front, with live
|
|
progress
|
|
|
|
**Notes**
|
|
- Timestamped notes per lesson, capturable via a keyboard shortcut without
|
|
leaving the player
|
|
- Notes Hub: every note across the whole library, searchable, in one list
|
|
- Per-course study guide export (markdown, organized by section)
|
|
|
|
**Personalization** (Settings)
|
|
- 54 built-in themes (grouped in the dropdown: Base, Editor Themes, and 5
|
|
categories of website-inspired schemes sourced from Figma's "53 Unique
|
|
Website Color Schemes" - 42 of the 53 kept, after cutting a couple of
|
|
neon-bright ones and several that ended up near-duplicates of another
|
|
scheme once mapped into actual UI colors), or a fully custom accent
|
|
color, corner radius, and card style
|
|
- Font, text size, page width, and spacing density
|
|
- Default library path override + manual "Refresh Library" (bypasses the
|
|
5-minute filesystem-scan cache)
|
|
|
|
**Backup & integrations**
|
|
- One-click backup export and restore: settings, hidden-path choices, the
|
|
Next Up queue, recent-view history, Outline config, and every course's
|
|
progress/notes as a zip (not the course files themselves). Restore
|
|
overwrites current data and needs a matching course folder to already
|
|
exist for each course's progress to land - it's a "put my data back"
|
|
action, not a merge
|
|
- Optional [Outline](https://www.getoutline.com/) integration — push a
|
|
lesson's notes to an Outline document/collection
|
|
|
|
---
|
|
|
|
## Data files
|
|
|
|
Everything under `OFFLINEU_DATA_DIR` (app-wide, not tied to a course):
|
|
|
|
| File | Contents |
|
|
| ---------------------- | ------------------------------------------ |
|
|
| `settings.json` | Theme, layout, library path, etc. |
|
|
| `hidden_paths.json` | Courses/folders curated out of the browser |
|
|
| `next_up.json` | The Next Up queue, in order |
|
|
| `recent_views.json` | Cross-course "Recently Viewed" history |
|
|
| `outline_config.json` | Outline API token/collection mapping |
|
|
|
|
Per-course, written inside the course's own folder on the library volume:
|
|
|
|
| File | Contents |
|
|
| ----------------------------------- | -------------------------------------------------------- |
|
|
| `.offlineu_progress.json` | Per-lesson completed/progress/duration/notes |
|
|
| `.offlineu_duration_cache.json` | ffprobe duration cache, keyed by (path, size, mtime) |
|
|
| `.offlineu_thumbnail.jpg` | Auto-generated cover art (only if no manual cover exists) |
|
|
|
|
---
|
|
|
|
## Folder structure example
|
|
|
|
```
|
|
MyCourse/
|
|
├── Section 1/
|
|
│ ├── 01 - Intro.mp4
|
|
│ ├── 02 - Setup Guide.pdf
|
|
│ └── 03 - Quiz.html
|
|
├── Section 2/
|
|
│ ├── 04 - Advanced Tips.mp4
|
|
│ └── resources/
|
|
│ └── extras.md
|
|
├── .offlineu_progress.json ← created automatically
|
|
├── .offlineu_duration_cache.json ← created automatically
|
|
└── .offlineu_thumbnail.jpg ← created automatically, only if no manual cover exists
|
|
```
|
|
|
|
No metadata files needed — course/section/lesson names come straight from
|
|
folder and file names.
|
|
|
|
## Supported file types
|
|
|
|
| Type | Extensions |
|
|
| --------- | ------------------------------------------------------------------ |
|
|
| Video | `.mp4` `.mkv` `.avi` `.mov` `.webm` `.m4v` `.flv` `.wmv` |
|
|
| Audio | `.mp3` `.wav` `.m4a` `.aac` `.ogg` `.flac` |
|
|
| Documents | `.txt` `.md` `.html` `.htm` `.pdf` `.docx` `.doc` `.rtf` |
|
|
| Subtitles | `.srt` `.vtt` `.ass` `.sub` `.sbv` |
|
|
| Quizzes | Any doc file whose name contains `quiz`, `exam`, `test`, `assessment`, `exercise`, `assignment`, or `homework` |
|
|
|
|
---
|
|
|
|
## Tools
|
|
|
|
`tools/generate_website_scheme_themes.py` is the one-off script that
|
|
produced the 42 "website scheme" theme entries in `THEME_PALETTES` (of
|
|
53 candidates - 11 were cut after visual review, 2 for a neon-bright
|
|
accent and 9 for landing too close to another kept scheme once mapped
|
|
into actual UI colors) - not part of the running app, kept for reference
|
|
if those palettes ever need regenerating or extending. See its module
|
|
docstring for how it turned Figma's color-scheme screenshots into actual
|
|
UI palettes.
|