👀🐶 Video Server
|
Some checks failed
ci/woodpecker/push/build Pipeline failed
Replace directory-only lookup with a scored query: +3 same channel, +2×N shared tags, +1 same directory, RANDOM() tiebreaker for variety. Returns up to 20 results across the whole library so there's always something related. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|---|---|---|
| .woodpecker | ||
| docs | ||
| migrations | ||
| src | ||
| .dockerignore | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| config.toml | ||
| devenv.lock | ||
| devenv.nix | ||
| docker-compose.yml | ||
| Dockerfile | ||
| README.md | ||
| renovate.json | ||
WatchDogs
A self-hosted media server for organizing and serving local videos, images, and galleries. Exposes a JSON API designed to power custom frontends — a YouTube-style video browser, a TikTok-style image feed, a gallery viewer, and more.
Resource Kinds
WatchDogs manages three types of media:
- Videos —
.mp4,.mkv,.webmfiles with metadata, thumbnails, and streaming - Images — Individual image files served via the API
- Galleries — Named collections of images, browsable as a set
Features
- yt-dlp metadata — Extracts and stores metadata produced by yt-dlp (title, description, uploader, views, tags, upload date, channel)
- Full-text search — PostgreSQL-backed search using websearch syntax and trigram similarity for fuzzy matching
- Thumbnails — Auto-generates thumbnails via FFmpeg; also supports custom sidecar images (
.jpg,.jpeg,.png,.avif) - Watch history — Per-user watch history tracking
- Multi-user auth — Session-based authentication with Admin and Regular roles, bcrypt password hashing, and CSRF protection
- Private mode — Optionally require login to access all content
- Open Graph Protocol — OGP metadata for video and thumbnail sharing
- JSON API — Machine-readable endpoints for building any kind of frontend on top
Configuration
Edit config.toml before starting:
[general]
private = true # Require login to access content
allow_ogp = true # Serve thumbnails publicly even if private (for OG embeds)
root_url = "http://127.0.0.1:8080"
video_path = "./videos" # Path to your media directory
Environment variables (Docker):
| Variable | Description |
|---|---|
DATABASE_URL |
PostgreSQL connection string |
RUST_LOG |
Log level (default: info) |
ROCKET_ADDRESS |
Bind address (default: 0.0.0.0) |
API Routes
All /api/* routes require an API token (APIUser auth via the based framework).
Videos
| Route | Description |
|---|---|
GET /api/videos?limit=N&offset=N |
Paginated list of videos, newest first |
GET /api/videos/<id> |
Single video with full metadata |
Images
| Route | Description |
|---|---|
GET /api/images?limit=N&offset=N |
Paginated list of images, newest first |
GET /api/images/<id> |
Single image metadata |
Galleries
| Route | Description |
|---|---|
GET /api/galleries |
All galleries |
GET /api/galleries/<id> |
Gallery with its full image list |
Tags & Channels (yt-dlp)
| Route | Description |
|---|---|
GET /api/tags |
All distinct tags |
GET /api/tags/<tag> |
Media with a specific tag |
GET /api/channels |
All distinct channels with metadata |
GET /api/channels/<id> |
Media from a channel |
Search & Generic
| Route | Description |
|---|---|
GET /api/search?query=X&limit=N&offset=N |
Full-text + trigram search across all media |
GET /api/media/<id> |
Any media by ID (video or image) |
Web UI
| Route | Description |
|---|---|
GET / |
Home — random videos, latest, galleries, directories |
GET /latest?offset=N |
Paginated recent videos (infinite scroll) |
GET /latest.json |
JSON: recent videos (no auth) |
GET /search?query=X&offset=N |
Search results |
GET /d/<directory> |
Videos in a directory |
GET /d/<directory>.json |
JSON: directory videos (no auth) |
GET /galleries |
All galleries grid |
GET /gallery/<id>?offset=N |
Gallery image grid (infinite scroll) |
GET /image?v=<id>&gallery=<id> |
Single image view with prev/next nav |
GET /watch?v=<id> |
Video player |
GET /video/raw?v=<id> |
Stream video file |
GET /video/thumbnail?v=<id> |
Thumbnail for any media (video or image) |
GET /image/raw?v=<id> |
Full-size raw image file |
GET /history |
User watch history |
GET /account |
Account page |
GET /login |
Login form |
POST /login |
Submit login |
GET /passwd |
Change password form |
POST /passwd |
Submit password change |
Database Schema
| Table | Purpose |
|---|---|
videos |
Core video metadata |
youtube_meta |
Embedded YouTube metadata |
youtube_meta_tags |
Tags (many-to-many) |
youtube_meta_categories |
Categories (many-to-many) |
video_thumbnail |
Stored thumbnail data |
users |
User accounts |
user_session |
Active sessions |
video_history |
Per-user watch history |
Migrations are applied automatically on startup.
Use Cases
- Personal video archive with search and browsing
- Archiving yt-dlp downloads with preserved metadata
- Image gallery server for a local photo or art collection
- Internal media hosting for a small team
- Building a custom frontend on top of the JSON API — a YouTube-style video browser, TikTok-style image feed, gallery viewer, etc.