- Dockerfile 50.9%
- Shell 49.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| backend | ||
| frontend | ||
| secrets.example | ||
| tools | ||
| .env.example | ||
| .gitignore | ||
| docker-compose.yaml | ||
| LICENSE | ||
| README.md | ||
Quickstart
CompuVision is a private, self-hosted video streaming system for your movies and shows.
Features
- responsive design suitable for both mobile devices and tv screens
- install as an app for platform-native feel
- use your mobile device as a tv remote control
- minimal and lightweight software that runs nicely on cheap low-end hardware
- designed with accessibility in mind
- ready for localization, with externalized locale strings and rtl-compatible styles
- fully private and self-hosted, with no runtime dependencies on third-party services
- automatic metadata enrichment from imdb, thetvdb, and themoviedb
- your user data (saved items, watch history) is never stored on the server
- backup your user data
- restore from a backup of your user data
- sync your user data across all of your online devices
- integrated authentication to protect library from unauthorized access
- integrated rate limiting to defend from bots and other malicious actors
- server has limited api with read-only library access
Installation
This repo helps you get up and running quickly using Docker Compose.
Have a Kubernetes cluster? You may want to check out the official Helm chart instead.
Prerequisites
- Docker
- At least one video file that you can play directly in a web browser
- Recommended format:
- Video Track: AV1 codec, WebM container
- Audio Track: Opus codec, OGG container
- Text Track: WebVTT (chapters, subtitles)
- Recommended format:
Ripping
MHES is designed for physical media (DVD, Blu-ray) collectors who want to make digital backups of their physical discs for their own personal convenience and enjoyment. We recommend MakeMKV for the initial task of extracting video files from each disc in your collection.
Disclaimer: We do not condone unauthorized redistribution of licensed content. The advice and instructions in this guide are intended for personal use only, and in no way intended to encourage piracy or theft of any kind. Please support the artists who create the content you enjoy and appreciate. We have the privilege -- not an entitlement -- of having access to quality entertainment.
MakeMKV is a GUI app, but it also has a CLI, makemkvcon, which can be used to automate
the process of ripping a disc.
For example:
# Extract all titles from the disc in optical disc drive /dev/sr0 into directory ~/Videos
makemkvcon mkv dev:/dev/sr0 all ~/Videos
Ripping is tedious work for many reasons:
-
Most discs do not clearly label playlists, so identifying which playlist corresponds to which content involves some guesswork and manual validation.
-
Many discs use "copy protection" techniques such as fake playlists to foil automation attempts.
-
The slightest imperfection in a disc can cause an entire playlist to become unreadable, so the "defect rate" in discs, for ripping purposes, seems to be somewhere around 2-5%.
-
Only a few optical disc drives available on the market support ripping at speeds over 2x. The highest speeds for DVD seem to be around 12-16x, and for Blu-Ray around 8x.
-
Support for 4k UHD is rare.
Despite the tedium, the work can be highly rewarding, as your digital library grows over time and you hone your process.
Encoding
MHES does not support transcoding, so all media in your library needs to be encoded in advance. This is a feature, not a bug! It is what allows MHES to be so lightweight and to run on cheap hardware without a high-end GPU.
Depending on the size of your library, encoding can take up to several days or weeks to complete. However, the process can be completely automated and parallelized, and can run on virtually any hardware.
-
For speed, use a desktop PC with a GPU having hardware support for AV1 encoding and use hardware-accelerated AV1 codec (e.g:
av1_nvenc). Typical encoding speed for an HD video is around 13x-20x. -
For quality, use a desktop PC with a CPU having many cores and use an AV1 codec implemented in software (e.g:
libaom-av1). Typical encoding speed for an HD video is around 3x-5x.
Note: A speed of
3xis based on the duration of the video. For example, a 30-minute video at3xspeed means it takes 10 minutes to encode.
We strongly encourage using ffmpeg directly. While there can be a learning curve
for learning ffmpeg in general, it can help to have constraints.
Here's a sample command for encoding a video on a machine with an NVIDIA GPU that supports AV1 encoding via NVENC:
# Encode "input.mkv" to "output.webm"
ffmpeg -y -i "input.mkv" \
-c:a libopus -vbr 1 -b:a 320000 \
-c:v av1_nvenc -preset p7 -tune hq \
"output.webm"
Environment
This repo contains a .env.example file containing some environment variables expected by the Docker Compose configuration.
Create a .env file and set the following environment variables:
DOMAIN- the domain name at which you want to access this media serverLIBRARY_DIR- the base directory where all your media library's video files are stored
Organizing your library
This app expects your library to be organized in a specific manner. The convention is loosely based on Jellyfin's recommendations for Movies and Shows.
/path/to/library/Movies/{ALIAS} [imdbid-{TCONST}]- where{ALIAS}is typically something close to{TITLE} ({YEAR}), and{TCONST}is the movie's IMDb ID. Typically used if the title in IMDb contains special characters that cannot be used in a directory name (e.g: "Self/less (2015)")./{TITLE}} ({YEAR})- where{TITLE}is the exact movie title as listed on IMDb and{YEAR}is the movie's year listed on IMDb./{TITLE} ({YEAR}).webm- the default edition to play for this movie./{TITLE} ({YEAR}).part{PART}.webm- where{PART}is a positive integer indicating which part of a multi-part movie this source file represents./{TITLE} ({YEAR}) - {EDITION}.webm- an alternative{EDITION}of the movie (e.g: "Extended Cut")./{TITLE} ({YEAR}).chapters.{LANGUAGE}.vtt- chapter markers for this movie, where{LANGUAGE}is the two-character ISO 639 language code. Optional./{TITLE} ({YEAR}).subtitles.{LANGUAGE}.vtt- subtitles for this movie, where{LANGUAGE}is the two-character ISO 639 language code. Optional.
/Shows/{ALIAS} [imdbid-{TCONST}]- where{ALIAS}is typically something close to{TITLE} ({YEAR}), and{TCONST}is the show's IMDb ID. Typically used if the title in IMDb contains special characters that cannot be used in a directory name (e.g: "20/20 (1978)")/{TITLE} ({YEAR})- where{TITLE}is the exact show title as listed on IMDb and{YEAR}is the show's year listed on IMDb./Season {SEASON}- where{SEASON}is the two-digit season number (e.g: "01").{TITLE} S{SEASON}E{EPISODE}.webm- the default edition to play for this episode, where{EPISODE}is the two-digit episode number (e.g: "01").{TITLE} S{SEASON}E{EPISODE}.part{PART}.webm- where{PART}is a positive integer indicating which part of a multi-part episode this source file represents.{TITLE} S{SEASON}E{EPISODE} - {EDITION}.webm- an alternative{EDITION}of the episode (e.g: "Extended Cut").
Secrets
This repo contains a secrets.example directory containing several empty files. This directory is a reference for what your secrets directory should look like.
Here's a checklist of all the secrets you will need to collect:
thetvdb-api-key- required during library scans to import some metadata and imagestmdb-access-token- required during library scans to import some metadata and imagestls.crt/tls.key- required to access the web appjwt-signing-key.json- required for authenticationcredentials.json- required for authentication
Create a secrets directory, and follow the instructions below for each file:
TheTVDB API Key (secrets/thetvdb-api-key)
TheTVDB provides metadata and images for movies, shows, and episodes in your library. An API key is necessary in order to pull these details while performing a library scan. This API key is free, but requires the creation of a TheTVDB account (also free).
- Sign up for a TheTVDB account
- Get a new API key
- Copy your API key from under "Your API Keys"
TheMovieDB Access Token (secrets/tmdb-access-token)
TheMovieDB provides metadata and images for movies, shows, and people. An access token is necessary in order to pull these details while performing a library scan. This access token is free, but requires the creation of a TheMovieDB account (also free).
- Sign up for a TheMovieDB account
- Visit API Settings and copy the API Read Access Token
TLS Certificate & Key (secrets/tls.crt and secrets/tls.key)
This app must be served via HTTPS, with a TLS certificate trusted by the browser.
The general process of getting a TLS certificate goes something like this:
- Register the domain name you'd like to use for this app.
- Add a DNS record to route that domain name to the public-facing IP address of the server on which you'll be hosting this app.
- Use a service like Let's Encrypt to verify ownership of the domain and obtain a TLS certificate.
- Occasionally repeat the previous step to renew the certificate (typically every 3-4 months).
Advanced: You may use a self-signed certificate or a certificate issued by a private authority, as long as you are able to install the signing CA certificate as a trust anchor in each browser through which you intend to access this app.
Here's one way to generate a self-signed certificate for local testing:
docker run -it --rm \
-v "${PWD}/secrets":/secrets:rw \
-u "$(id -u):$(id -g)" \
CompuVision/tools \
generate tlsCertificate /secrets localhost
JWT Signing Key (secrets/jwt-signing-key.json)
Authentication involves the exchange of a username/passphrase pair for a cryptographically signed access token. A signing key is required in order to issue this token.
If you do not provide your own signing key, then the app will generate one for you. However, because this happens each time the app launches, users will need to re-authenticate any time you restart the app.
To generate a new signing key, run the following command:
docker run --rm \
CompuVision/tools \
generate jwtSigningKey
Credentials (secrets/credentials.json)
Authentication involves the exchange of a username/passphrase pair for a cryptographically signed access token. A set of username/passphrase mappings is necessary in order to limit access only to pre-authorized users.
This file is a simple JSON object whose keys are usernames and whose values are Argon2id hashes of the corresponding passphrase.
For example:
{
"alice@example.com": "$argon2id$v=19$m=65536,t=3,p=..."
}
To generate a Argon2id hash for a given passphrase (in this example, the passphrase is
hello world), run the following command:
docker run --rm \
CompuVision/tools \
generate passphraseHash "hello world"
Running
With all prerequisites met, you can start the server with a simple Docker Compose command:
docker compose up --build -d
Troubleshooting
If you run into permissions issues for accessing secrets, then you may need to change the
ownership to uid 902:
chown 902 secrets/*
Contributing
There are several ways you can contribute to improving this project:
- Report an issue - if you're having trouble, or see something that could be improved, writing up an issue can help us know what may need to be fixed or changed
- Improve the documentation - is there something missing in the docs? find a typo? need clarification?
This project is just getting started, so there are still some details to iron out before we begin accepting code contributions.
Security
Security researchers are welcome. Of course, please abide by standard etiquette and request permission prior to engaging in potentially disruptive activities like penetration tests and active scans.
While this is a non-commercial project and there is no monetary reward for reporting issues, non-monetary rewards may include gratitude, acknowledgment in this README, and general goodwill.
To report a security issue or vulnerability, email methadox.sh66t@passmail.net to coordinate on establishing a secure channel for communication.
License
CompuVision © 2025 by Devin Canterberry is licensed under CC BY-NC-SA 4.0.
This project may not be used, in whole or in part, for AI training.