A secure personal Media Server, Video Manager, and Streaming application built with a Python (Flask) backend and a React + TypeScript frontend. Styled using Tailwind CSS and powered by the Vidstack Player, it offers native seekable playback, on-the-fly subtitle extraction, and interactive hover seek-previews.
srv-stream is designed to turn any local directory of videos and media files into a self-hosted, streaming platform. Whether running on a home server, NAS, or remote VPS, it indexes your videos, tracks media details dynamically, and serves them with full seekability.
Unlike traditional heavy media servers, srv-stream is highly efficient and lightweight. It relies on standard browser media engine capabilities, utilizing smart server-side helpersβsuch as FFmpeg for subtitle demuxing, and OpenCV for spatial storyboard generationβto deliver an incredibly premium streaming experience directly in the browser.
- Native Byte-Range Seeking: Engineered with a custom HTTP byte-range streamer enabling instant seek capabilities in MKV, MP4, and WebM videos without full file buffering.
- On-the-fly Subtitle Extraction: Dynamically extracts specific internal subtitle tracks (VTT/SRT) from video containers (e.g., MKV) using FFmpeg on demand, serving them instantly to the browser player.
- Hover-Seek Storyboard Previews: Automatically generates a
10x5spatial image grid (storyboard.jpg) and a mapping.vttfile (thumbnails.vtt) for hover seek-previews in the video player using OpenCV in a background thread. - Premium Vidstack Player: Integrates the state-of-the-art Vidstack Media Player for fluid controls, keyboard shortcuts, quality/track menus, and elegant styling.
- Track Inspection: Employs
ffprobeto scan video containers and index all audio/subtitle streams automatically.
- Hierarchical Tree & Browser: Explore files easily using an interactive directory tree sidebar or a visual grid/list content viewer.
- Full File Operations: Perform full-fledged management tasks directly in the browser: Copy, Move, Rename, and Delete files and directories.
- Background Thumbnails: Crawls newly added directories in the background, extracting keyframes to generate beautiful video thumbnails without UI blocking.
- Local Multi-file Uploader: Drag, drop, and upload multiple video files directly into your active workspace directories.
- Asynchronous Remote URL Downloader: Pass any direct HTTP download link (along with custom headers, cookies, and SSL bypass toggles) and download media files directly to the server in a background worker thread with real-time status and progress tracking.
- Token-Based Authentication: All streaming, browsing, file management, and upload endpoints are fully protected using secure token authentication (sent via Cookies, Authorization Header, or query params).
- Caching Layer: File durations, track layouts, and spatial storyboard images are securely cached in localized
.essentialssubdirectories, reducing CPU overhead during repeated browsing.
- Python 3.9+ & Flask (Micro-framework)
- OpenCV (
opencv-python) (Frame & storyboard generation) - Pillow (
PIL) (Image optimization and processing) - FFmpeg / FFprobe (Stream inspection and subtitle extracting)
- Flask-CORS (Secure cross-origin handling)
- python-dotenv (Environment configuration)
- Gunicorn (Production WSGI HTTP server)
- React 18 & TypeScript
- Vite (Next-gen frontend toolchain)
- Tailwind CSS (Utility-first CSS styling)
- Vidstack Player (Premium web media player framework)
- Axios (Promise-based HTTP client)
The application is configured using environment variables. Before running, create a .env file in the root directory:
SECRET_TOKEN=your_secure_random_token_here
ROOT_DIR=/absolute/path/to/your/media/library
PORT=4000| Variable | Description |
|---|---|
SECRET_TOKEN |
Bearer token used to authenticate frontend requests. Keep this strong and private. |
ROOT_DIR |
The base path on the server where all video and audio files are stored. The web app is sandboxed to this folder. |
PORT |
The port the Flask backend will listen on (defaults to 4000 in Docker). |
You can run srv-stream either natively (for local development) or as a containerized stack using Docker.
This is the easiest and most robust method because all dependencies (FFmpeg, FFprobe, OpenCV development libraries, Node, and Python) are pre-packaged and configured.
- Ensure you have Docker and Docker Compose installed.
- Clone the repository and configure your
.envfile in the project root:cp env.exaple .env # Edit .env with your desired configuration - Set your
/path/outside/rootdirunder the volume configuration indocker-compose.ymlto match your local media folder. - Run the compose environment:
docker-compose up -d --build
- Open your browser and navigate to
http://localhost:4000. Log in using your configuredSECRET_TOKEN.
- Install system dependencies:
- macOS:
brew install ffmpeg - Ubuntu/Debian:
sudo apt update && sudo apt install -y ffmpeg libgl1-mesa-glx
- macOS:
- Create and activate a Python virtual environment:
python3 -m venv .venv source .venv/bin/activate - Install backend dependencies:
pip install -r requirements.txt
- Start the backend:
python3 server.py
- Navigate to the
frontendfolder:cd frontend - Install npm packages:
npm install
- For development with Hot Module Replacement (HMR):
(Ensure you update the API baseURL or setup a proxy to route
npm run dev
/browse,/stream, etc. to the Flask backend running on port 4000) - To build the production frontend bundle served directly by Flask:
The build files will compile into
npm run build
frontend/dist, which the Flask app is configured to serve automatically at/.
graph TD
Client[Browser / Vidstack Player] -->|1. Browse / Tree / Operations| Flask[Flask API Server]
Client -->|2. Stream Request with Range| Flask
Client -->|3. Get Subtitle Track index| Flask
Flask -.->|Scan Container| FFprobe[FFprobe Process]
Flask -.->|Extract Stream on-the-fly| FFmpeg[FFmpeg Process]
Flask -.->|Async Thumbnailing / Storyboard| OpenCV[OpenCV & Pillow]
FFprobe -->|Metadata JSON| Flask
FFmpeg -->|WebVTT Stream| Flask
OpenCV -->|Cached jpg / vtt| Filesystem[(.essentials/ Cache)]
Flask -->|Serve API, Media Chunk, or Subtitles| Client
When the browser requests a chunk of video using the Range header (e.g. bytes=1048576-), services/video_services.py intercepts the request. It parses the start and end offsets, dynamically computes content lengths, reads the requested chunk from disk in high-performance blocks (4KB buffers up to a max chunk size of 5MB), and responds with an HTTP 206 Partial Content status. This makes video seeking instantaneous and eliminates buffering lags.
Most modern web video engines do not natively parse subtitle formats inside .mkv files. To solve this, srv-stream maps available subtitle track indices via ffprobe. When a subtitle track is activated in the Vidstack UI, the backend runs a subprocess:
ffmpeg -v error -i <video_file> -map 0:<track_index> -f webvtt pipe:1The output stream is piped dynamically directly into the HTTP response. This extracts subtitles on the fly with zero disk-write footprint.
To make seeking interactive, the app implements hover preview sprites:
- OpenCV parses the video file and samples 50 frames uniformly distributed throughout the length.
- It arranges them in a
10x5grid and saves a single lightweight image (storyboard.jpg) inside the.essentialscache folder. - It creates a
.vttfile (thumbnails.vtt) which maps timeline offsets (e.g.00:01:20.000 --> 00:01:23.000) to specific spatial coordinates in the image grid (storyboard.jpg#xywh=180,202,180,101). - The Vidstack player reads this VTT file to display accurate, real-time video preview thumbnails on hover.
All endpoints require authentication bearer tokens via:
Authorization: Bearer <SECRET_TOKEN>header, ORAuth_tokencookie, ORtokenURL query parameter.
| Endpoint | Method | Description |
|---|---|---|
/browse |
GET |
Lists directories and files for a path; triggers async thumbnailing. |
/tree |
GET |
Generates a full recursive directory tree mapping for the sidebar. |
/file/info |
GET |
Returns duration, size, metadata, and tracks (audio/subtitles). |
/file/delete |
POST |
Deletes a list of files or directories. |
/file/rename |
POST |
Renames a file or folder. |
/file/move |
POST |
Moves file(s) to a target directory. |
/file/copy |
POST |
Copies file(s) to a target directory. |
/stream |
GET |
Streams a video file supporting HTTP Byte-Ranges. |
/subtitle |
GET |
Dynamically streams a subtitle track extracted as WebVTT. |
/storyboard.jpg |
GET |
Returns the 10x5 grid storyboard image. |
/thumbnails.vtt |
GET |
Returns the WebVTT file mapping times to storyboard coordinates. |
/thumbnail |
GET |
Returns the keyframe preview thumbnail for folder browsing. |
/upload |
POST |
Uploads local video files to the specified directory. |
/upload/remote |
POST |
Initiates an async URL download in a background thread. |
/upload/remote/status/<id> |
GET |
Returns download status, progress, download speed, and size. |
- Make sure
ffmpegandffprobeare present in your system's PATH. - All essential metadata caches (thumbnails, storyboards, track info, durations) are saved as hidden
.essentials/folders inside your media directory. Do not delete them unless you want the server to re-generate the assets. - Built with β by geethakash. Feel free to submit pull requests or report issues.
