Files
offlineu/README.md
T
rmsitzandClaude Sonnet 5 52371dd02f Convert all Figma theme backgrounds to dark/colored, dedupe again
The 34 kept themes were split 24 light-mode / 18 dark-mode - the light
ones all shared the same washed-out near-white background (L=0.95),
too bright per feedback. Added a force_mood override to build_theme()
and regenerated all 24 as dark instead, same accent character, colored-
dark background rather than near-white.

That conversion collapsed a lot of previously-distinct light backgrounds
into similar dark neutrals, so re-ran the same color-distance dedup pass
across the full set: 8 more turned out to be near-twins once everything
converged to dark (including a 3-way orange-on-dark cluster trimmed to
one). Net: 53 candidates -> 34 kept, all dark/colored, zero white
backgrounds - down from the 42 (24 light/18 dark) shipped last commit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 08:33:34 -04:00

213 lines
9.0 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.
![Lesson view](images/lesson-0-8-2025-08-04-04_58_17.png)
*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)
- 46 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" - 34 of the 53 kept, all as dark/colored
backgrounds rather than the source page's mostly-white mockups, after
cutting a couple of neon-bright accents and pruning near-duplicates by
actual color distance), 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 34 "website scheme" theme entries in `THEME_PALETTES` (of
53 candidates - all 24 light-mode ones were converted to dark/colored
backgrounds per request, then 19 were cut across two review passes: 2
for a neon-bright accent, 17 for landing too close to another kept
scheme once mapped into actual UI colors, since forcing everything dark
made several collide that hadn't before) - not part of the running app,
kept for reference if those palettes ever need regenerating or
extending. `build_theme()` takes a `force_mood='dark'|'light'` override
for exactly this kind of bulk remapping. See its module docstring for
how it turned Figma's color-scheme screenshots into actual UI palettes.