Replace the original upstream template README (public-project framing, roadmap items already built, links to a different GitHub repo) with documentation matching the actual private deployment: real docker-compose.yml, the Gitea->Dockhand rebuild-on-push behavior, env vars/CLI flags, data file locations, and the full current feature set (theming, notes hub, video duration/ffprobe caching, backup export, Outline integration) that had never been documented. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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):
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
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
- 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, 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 — 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
ffprobeand 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 - Settings → "Precompute Video Lengths" walks the whole library in the background to populate that cache 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)
- Built-in themes or a 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: settings, hidden-path choices, recent-view history, and every course's progress/notes as a zip (not the course files themselves)
- Optional Outline 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) |
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
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 |