A self-hosted video streaming server for personal media libraries. Stream your MKV, MP4, and AVI files from anywhere with real-time FFmpeg transcoding, rich metadata from TMDB/iTunes/TVmaze, and simple PIN-based device authentication.
🎬 Your media. Your server. Your rules. A complete, self-hosted streaming stack that runs anywhere Docker does — no accounts, no cloud, no telemetry.
🐳 One-command deploy • ⚡ GPU-accelerated • 📦 Zero build step • 🏠 100% self-hosted
- 🎞️ Real-time transcoding of MKV, MP4, AVI, and MOV with FFmpeg — no pre-conversion, ever
- ⚡ GPU acceleration (Intel VA-API / QuickSync, AMD, and NVIDIA NVENC) with automatic software fallback
- 📺 Adaptive quality on the fly: native → 720p → 480p → 240p → 144p → audio-only
- 🔊 Smart audio — 5.1 sources downmixed to clear stereo so dialogue never gets lost
- 💬 On-the-fly subtitles extracted to WebVTT straight from the source file
- ⏯️ Resume & continue-watching with watch history synced across every device
- 🖼️ Gorgeous poster wall with auto-detected Movies, Shows, and Anime libraries
- 🧠 Triple-source metadata scraping — TMDB → iTunes → TVmaze fallback, posters and all
- 🔎 Instant search, sort, and season grouping for libraries of any size
- 🧑🤝🧑 Actor tagging with one-click "more from this actor" discovery
- 🎬 Trailer & video downloaders built in: TMDB trailers, YouTube channels (via
yt-dlp), and Archive.org - 🧩 Browser extension to send downloadable videos straight into your library
- 🤖 Optional AI upscaling through a remote GPU worker (Real-ESRGAN x4)
- 📟 PIN-based device pairing with a custom JWT implementation — grant or revoke per device
- 🔒 HTTPS out of the box with an auto-generated self-signed certificate
- ⌨️ Fully keyboard-driven — power-user fast, mouse entirely optional
- 🐳 Deploy in one command with Docker Compose
- 📦 No build step, no framework — server-rendered HTML and vanilla JS
- 🏠 Truly yours — no accounts, no cloud dependencies, no phoning home
The fastest way to run VibeStream is Docker — it bundles every dependency:
docker compose up -d --buildOpen http://localhost:3000 and enter the device PIN from docker compose logs -f vibestream. See Run with Docker for volumes, ports, and GPU acceleration.
Requires Node.js 18+, FFmpeg/FFprobe in PATH, and optionally yt-dlp for YouTube downloads.
npm install
node server.jsOpen http://localhost:3000. A device PIN will print to the console on first visit.
| Variable | Default | Description |
|---|---|---|
PORT |
3000 |
HTTP listen port |
HTTPS_PORT |
443 |
HTTPS listen port (self-signed cert auto-generated) |
VIDEO_DIR |
./videos |
Media library root |
DATA_DIR |
./.appdata |
Settings, device tokens, JWT secret |
HWACCEL |
(unset) | GPU encoder: vaapi, qsv, or nvenc. Unset = software. See GPU Acceleration |
All runtime configuration is done through the web UI at /settings:
- TMDB API Key — Get a free key at themoviedb.org
- Storage paths — Visible on the settings page to confirm mount points
- Device management — Grant/revoke device access
The included docker-compose.yml builds the image from source and runs it — no registry or external image needed. From the project root:
docker compose up -d --buildThen open http://localhost:3000. On first visit a device PIN prints to the container log — grab it with docker compose logs -f vibestream and enter it to register your browser.
The compose file ships with sensible defaults:
| Setting | Default | Notes |
|---|---|---|
| Media library | ./videos → /data/videos |
Change the left side to point at your media folder |
| App data | ./appdata → /data/appdata |
Settings, device tokens, metadata cache, TLS certs |
| HTTP | host 3000 |
Plain HTTP |
| HTTPS | host 8443 |
Self-signed cert auto-generated on first run |
The image bundles FFmpeg, yt-dlp, and the Intel VA-API drivers, so transcoding and downloads work out of the box. By default it uses software encoding, which runs anywhere. To use a GPU, see below.
Common commands:
docker compose logs -f vibestream # view logs (and the first-run PIN)
docker compose restart vibestream # restart
docker compose down # stop and remove the container
docker compose up -d --build # rebuild after pulling new codeHardware transcoding is optional. Leave it off and VibeStream encodes in software — correct everywhere, just heavier on CPU. To offload encoding to a GPU, you set the HWACCEL environment variable and pass the GPU into the container. Both steps are pre-wired (commented out) in docker-compose.yml.
Supported HWACCEL values:
| Value | Hardware | FFmpeg encoder |
|---|---|---|
| (unset) | none — software | libx264 |
vaapi |
Intel iGPU / AMD GPU (VA-API) | h264_vaapi |
qsv |
Intel QuickSync | h264_qsv |
nvenc |
NVIDIA GPU | h264_nvenc |
If a GPU is requested but the encode test fails at startup, VibeStream logs a warning and automatically falls back to software — it will not crash.
In docker-compose.yml, uncomment the HWACCEL line and the /dev/dri passthrough block:
environment:
- HWACCEL=vaapi # or: qsv
# ...
devices:
- /dev/dri:/dev/dri
group_add:
- "44" # 'video' group GID — run on host: getent group video
- "104" # 'render' group GID — run on host: getent group renderThe group_add GIDs must match your host. Run getent group video and getent group render and substitute the numbers you get back.
Install the NVIDIA Container Toolkit on the host, then uncomment the HWACCEL=nvenc line and the deploy.resources block:
environment:
- HWACCEL=nvenc
# ...
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]Apply any of these with docker compose up -d. Confirm the GPU is active in the logs:
docker compose logs vibestream | grep hwaccelYou should see your accelerator verified (e.g. [hwaccel] VAAPI h264 encoding verified) rather than not set (software encoding).
The Run without Docker steps cover a plain Node.js install. GPU acceleration works there too — export HWACCEL before starting (e.g. HWACCEL=vaapi node server.js), provided the matching FFmpeg drivers are installed on the host.
Single-process Node.js/Express server with server-rendered HTML pages. No frontend build step, no framework. The entire backend is organized as:
server.js Entry point — Express setup, middleware, route registration
lib/
config.js Paths, env vars, path resolution
theme.js CSS theme variables and SVG assets
settings.js JSON file persistence for settings and device access
auth.js JWT implementation (no library), auth middleware
utils.js Filename parser, formatters, directory type detection
ffmpeg.js FFprobe metadata extraction
scrapers.js TMDB, iTunes, TVmaze metadata scrapers
downloads.js Download source definitions (TMDB trailers, YouTube, Archive.org)
routes/
auth-routes.js Login page, PIN registration, device management API
settings-routes.js Settings page and API
api-routes.js Library scan, metadata CRUD, file management
download-routes.js Download search/start/cancel, YouTube channels
native-api-routes.js JSON API (/api/library, /api/file, /api/recent)
stream-routes.js FFmpeg piped streaming, HLS, subtitle extraction
player-page.js Video player page with Video.js
library-page.js Library browser with grid layout, sorting, search, download modal
$VIDEO_DIR/
Movies/ Library directories (type set via UI dropdown or .library marker)
Shows/
.metadata/ Cached .meta (JSON) and .png (poster) files, keyed by base64url path
$DATA_DIR/
settings.json TMDB key, JWT secret, YouTube channels
access.json Registered devices
- Web browsers receive a piped fragmented MP4 stream from FFmpeg (
/stream/:id) - HLS is also available (
/hls/:id/stream.m3u8) for clients that can't handle piped HTTP streams - Audio is downmixed to stereo (
-ac 2) for proper center channel (dialogue) from 5.1 sources - HLS sessions are cached and restarted when stream parameters change
Library: S (search), arrows (navigate), Enter (open), Esc (back/home), I (info), E (edit title), D (download), U (upload), C (new folder), L (scan), R (refresh), O (cycle sort), Shift+O (toggle sort direction), Del (delete)
Download Modal: arrows (navigate results), Enter (download selected / search from input), Space (preview thumbnail), O (cycle sort), Shift+O (toggle sort direction), Esc (close)
Player: Space (play/pause), Left/Right (seek), Up/Down (volume), F (fullscreen), M (mute), T (theater), A (aspect ratio), C (crop), I (info), B (stats), Esc (back)
ISC