Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

43 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

FogTube

FogTube is a self-hosted, offline-first video library for classrooms, labs, and other private networks. It's a single package — uv tool install fogtube and run fogtube — that bundles its own Nuxt 4 web UI behind a FastAPI backend, no separate Node or Deno install required. Downloads and media processing happen in independent, durable workers, so browsing and playback stay responsive.

Only download media you have permission to copy. FogTube does not bypass DRM or grant rights to third-party content.

Features

  • Import one video, an entire playlist, or a channel with yt-dlp.
  • Watch an ingest folder and automatically process files dropped into it.
  • Normalize media to a browser-friendly format: H.264/AAC in MP4 by default, or AV1/VP9 with Opus in WebM.
  • Prefer NVENC, Intel Quick Sync, or VA-API hardware encoding when the GPU genuinely supports it, then safely fall back to software.
  • Keep videos and playlists private, internal (any signed-in user), or public.
  • Track per-user resume position, completion state, and 1–5 star ratings.
  • Create additional local accounts from the administrator-only Users page.
  • Preserve channel relationships even for videos downloaded individually.
  • Generate local playlists for remote playlists and channels.
  • Deduplicate remote videos while still linking them into every applicable playlist.
  • Subscribe to playlists and channels and periodically download new entries.
  • Coordinate multiple worker processes through a leased SQLite job queue—no Redis service required.
  • Stream media with HTTP range support through FastAPI/Starlette.

Install

FogTube ships as a single package with its web UI already bundled in — no Node, Deno, or just needed to run a release.

Requirements:

  • uv (manages the Python 3.13 runtime automatically)
  • FFmpeg and FFprobe on PATH
uv tool install fogtube

Then check the environment:

fogtube doctor

doctor reports on Python, ffmpeg/ffprobe, storage writability, port availability, and hardware encoders, and exits non-zero if anything fatal is wrong. If ffmpeg is missing, it prints the right install command for your OS (e.g. brew install ffmpeg on macOS, sudo apt install ffmpeg on Debian/Ubuntu, sudo dnf install ffmpeg on Fedora/RHEL, sudo pacman -S ffmpeg on Arch).

Run

fogtube

This starts the API, the ingest/subscription watcher, and two queue workers, waits for the API to become healthy, and opens FogTube in your browser at http://127.0.0.1:42731. Press Ctrl-C to stop the whole stack, including every worker, cleanly.

On first run, FogTube creates its storage root, config.yml, and a bootstrap admin account, and prints the generated credentials to the console:

Bootstrap username: admin
Bootstrap password: <generated>
FogTube is running at http://127.0.0.1:42731

Those two lines print only on the run that creates the account — every run after that starts the existing account without reprinting the password, so save it somewhere on first boot. If you miss it, it remains available afterward as bootstrap_password in config.yml in the storage root (see Storage layout).

Commands

Command What it does
fogtube Start the full stack and open a browser (equivalent to fogtube serve)
fogtube serve [--workers N] [--host HOST] [--port PORT] [--no-browser] Same, with overrides
fogtube init Create storage, configuration, and database, seed the bootstrap user, then exit; prints the bootstrap credentials
fogtube doctor Preflight report on ffmpeg, storage, ports, and hardware encoders
fogtube api Run only the FastAPI server
fogtube worker [--once] [--kinds inspect_remote,download] Run a durable worker, optionally restricted to job kinds
fogtube watcher [--once] Run only the ingest/subscription watcher

api, worker, and watcher are meant to run as independent, separately supervised processes pointed at the same storage root — one API process, N worker processes, and one watcher process — which is the shape a systemd unit per role, or one container per role, wants.

The storage root is a global option, so --home goes before the subcommand:

fogtube --home /srv/fogtube doctor
FOGTUBE_HOME=/srv/fogtube fogtube

Storage layout

<storage root>/
├── auth/
│   ├── user-ID.json
│   └── user-ID.cookies.txt
├── config.yml
├── fogtube.sqlite3
├── logs/
│   ├── api-PID.jsonl
│   ├── watcher-PID.jsonl
│   └── worker-PID.jsonl
├── data/
│   ├── videos/
│   ├── thumbnails/
│   └── staging/
└── ingest/

The storage root resolves in this order: the --home flag, the FOGTUBE_HOME environment variable, a previously recorded pointer file, an OwnTube home adopted via OWNTUBE_HOME (see Upgrading from OwnTube — this is not automatic), and finally the platform data directory (~/.local/share/fogtube on Linux, ~/Library/Application Support/fogtube on macOS). Run fogtube doctor to see which one is in effect.

Drop supported video files directly into ingest/. The default owner comes from ingest_default_user in the configuration, and folder-ingested videos start as private. A durable ingest record prevents the same file path from being processed twice.

To relocate all runtime state without editing config.yml or touching the platform default — handy for a second instance, a test run, or local development:

FOGTUBE_HOME=/tmp/fogtube-dev fogtube
FOGTUBE_HOME=/tmp/fogtube-dev just run   # from a checkout, see Development

Upgrading from OwnTube

FogTube is the renamed successor to OwnTube. Adoption of a pre-rename .owntube home is not automatic — no author-specific path ships in the package, so a bare fogtube on a machine with an existing .owntube install creates a brand-new, empty library at the platform default rather than finding your old one.

Adoption is detected only through the OWNTUBE_HOME environment variable, and only when no --home flag, no FOGTUBE_HOME, and no pointer file are already in play (--home/FOGTUBE_HOME take precedence and skip legacy detection entirely):

OWNTUBE_HOME=/path/to/your/.owntube fogtube init

This renames only the SQLite database (fogtube.sqlite3) and its -wal/-shm sidecars in place — no video data is moved or copied. It is idempotent, so running it again is harmless. It does not by itself record a pointer file, so either keep OWNTUBE_HOME exported (e.g. in your shell profile) for every future run, or, once, point --home at the same directory to write a pointer so later plain fogtube runs find it without the environment variable:

fogtube --home /path/to/your/.owntube init

Configuration

FogTube generates these defaults on first startup:

host: 127.0.0.1
port: 42731
frontend_origin: http://localhost:42732
bootstrap_username: admin
bootstrap_password: generated-on-first-start
ingest_default_user: admin
ingest_poll_seconds: 5
worker_poll_seconds: 1.0
worker_lease_seconds: 3600
subscription_poll_seconds: 60
subscription_interval_minutes: 360
subscription_archive_limit: 50
transcode_mode: auto
preferred_video_codec: h264
subtitle_languages:
  - en.*
session_days: 30

Set transcode_mode to always to normalize every file, auto to stream-copy input that is already in the target container and codec, or never to reject incompatible inputs.

preferred_video_codec also decides the output container, because the two cannot be chosen independently — WebM only carries VP8/VP9/AV1, and MP4 in practice only carries H.264/HEVC:

Setting Container Hardware encoders tried, in order Notes
h264 (default) .mp4 NVENC, Quick Sync, VA-API, then software libx264 Plays everywhere; the safest choice
hevc .mp4 NVENC, Quick Sync, VA-API, then software libx265 ~30% smaller, but Firefox and Linux browser support is inconsistent
av1 .webm NVENC, Quick Sync, then software libvpx-vp9 NVENC AV1 needs an RTX 40-series or newer card. On older GPUs this silently falls back to software VP9, which is roughly 6x slower than real time at 1080p
vp9 .webm VA-API, then software libvpx-vp9 No NVIDIA card can encode VP9 in hardware

Check what your machine actually selected — the encoder is probed with a real one-frame encode, so a codec listed by ffmpeg -encoders may still be rejected:

uv run python -c "from fogtube.config import AppConfig; from fogtube.media import encoder_candidates; print([c.name for c in encoder_candidates(AppConfig())])"

Existing .webm files keep playing after a codec change; the streaming endpoint sends a MIME type derived from each stored file rather than a fixed container.

Prepare existing MKV files with NVIDIA NVENC

For a large existing MKV collection, scripts/mkv-to-fogtube.sh creates a separate, FogTube-ready H.264/AAC MP4 tree using the NVIDIA encoder. It never modifies source files or overwrites output, preserves metadata/chapters and compatible text subtitles, and uses temporary .partial.mp4 files until each conversion completes:

./scripts/mkv-to-fogtube.sh /media/mkv-library /media/fogtube-ready

Drop the generated files into FogTube's ingest folder with the default h264 codec and transcode_mode: auto; FogTube will stream-copy them rather than transcode them again.

Authenticated downloads

Each user can open Settings to save a browser User-Agent and paste a Netscape-format cookies.txt export. Unless a user overrides it, downloads use Mozilla/5.0 (X11; Linux x86_64; rv:153.0) Gecko/20100101 Firefox/153.0. FogTube stores cookie files only on the server with owner-only permissions, never returns their contents through the API, and applies that identity to both remote discovery and downloads. Treat these files like passwords and use only accounts and media you are authorized to access.

For YouTube player challenges, FogTube explicitly enables yt-dlp's Deno JavaScript runtime and permits the official external EJS component from yt-dlp-ejs on GitHub. Deno must be available on the worker PATH, and the worker needs outbound access when yt-dlp has to retrieve that component. Cookies and a matching User-Agent can reduce anonymous-session friction, but they do not make requests indistinguishable from a browser or override provider limits.

Remote downloads prefer the same video codec and container configured for the final library. With the default H.264 setting, FogTube first asks yt-dlp for H.264/AAC MP4 so compatible videos can be stream-copied instead of re-encoded. Manual and automatic captions matching subtitle_languages are downloaded and embedded. Each subscription also has an editable archive limit (50 by default), so only its newest entries are considered during each refresh.

Architecture

Nuxt browser UI ──HTTP──> FastAPI ──> PostgreSQL
                              │
                              └──────> media files
                              │
                              └──enqueue──> SQLite jobs
                                               │
                       ingest/subscription watcher
                                               │
                                  N worker processes
                                  ├── yt-dlp discovery
                                  ├── yt-dlp download
                                  └── FFmpeg processing

Workers atomically claim jobs with a worker identity and lease. An interrupted job becomes claimable after its lease expires. The unique (provider, remote_id) video constraint is the final deduplication guard, including races across workers.

SQLite uses WAL mode, foreign keys, and a busy timeout. This queue design is distributed across local processes and can also coordinate processes sharing the same database and storage mount. SQLite is intentionally the single-node consistency boundary; use one shared host/filesystem rather than copying the database between machines.

Structured logs

FastAPI, Uvicorn, yt-dlp, the watcher, and workers are routed through Loguru. Each process writes newline-delimited JSON to <storage root>/logs/ while retaining concise console output. Files rotate at 25 MB and are retained for 30 days. API records include request ID, method, path, status, and elapsed milliseconds; request bodies, cookies, authorization headers, and query strings are deliberately excluded.

Development

The commands above are for an installed release. Working from a checkout uses just and runs the frontend from source instead of the bundled SPA:

  • Python 3.13 and uv
  • Deno 2.9 or newer
  • FFmpeg and FFprobe
  • just
just setup
just run

Open http://localhost:42732. just run starts the API, the Nuxt dev server, the ingest/subscription watcher, and two queue workers. Change the worker count when needed:

FOGTUBE_WORKERS=4 just run
just run       # full development environment
just api       # FastAPI only
just frontend  # Nuxt through Deno only
just worker    # one worker
just watcher   # ingest and subscription watcher
just format    # format Python and frontend code
just lint      # formatting, lint, and type checks
just test      # pytest suite
just security  # Bandit, pip-audit, and deno audit
just scan      # Deep source, secret, Kubernetes, and container-image scan
just container-scan ghcr.io/OWNER/fogtube:TAG  # Scan a published image
just build     # stage the SPA into the Python package
just check     # all verification above

The interactive API documentation is available at http://localhost:42731/docs while the backend runs.

Kubernetes

FogTube includes a production container build and Helm chart. The image runs Nuxt static generation during its build, copies .output/public into the Python package, and serves the resulting SPA and /api routes from the same FastAPI process and HTTP Service.

docker build -t registry.example.com/fogtube:0.1.0 .
docker push registry.example.com/fogtube:0.1.0
helm upgrade --install fogtube deploy/helm/fogtube \
  --namespace fogtube --create-namespace \
  --set image.repository=registry.example.com/fogtube \
  --set image.tag=0.1.0

See deploy/helm/fogtube/README.md for persistence, credentials, ingress, NetworkPolicy, GPU, and verification options. The chart installs PostgreSQL by default and runs downloader and transcoder workers independently. Tune concurrency with fogtube.downloaderWorkers and fogtube.transcoderWorkers.

About

A private, offline-first video library for schools and other local networks

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages