Self-hosted movies & TV from your NAS β own your living room, not a subscription
Status: π¬ pre-v1.0 β in design / pre-alpha. This README is the spec we're building toward, not a description of a finished app. The phased build plan lives in ROADMAP.md. NASCinema is multi-user and fully config-driven from day one β nothing is tied to one person's machine or storage (see Design principles).
NASCinema is the video sibling of NASRadio β a self-hosted movie & TV server that streams from your NAS to your desktop, phone, and living-room TV. It's built to fix the exact things that make Jellyfin frustrating and Plex expensive: subtitles that actually load, direct play that actually direct-plays, remote access that doesn't cost a subscription, and one app that looks the same everywhere.
It deliberately reuses NASRadio's proven backbone β authenticated API, scoped media tokens, pre-transcode cache, the 10-foot TV shell, Chromecast, in-app server config, and self-healing acquisition β and adds the video layer on top: TV-show hierarchy, a real subtitle pipeline, HDR handling, skip-intro, and a phone-as-remote living-room experience.
| Pain point | How NASCinema wins |
|---|---|
| Transcoding / playback | Every file is probed at scan. Remux-first (copy streams, ~0 CPU) before ever transcoding. A per-playback "why am I transcoding?" badge β the thing Plex/JF hide. Pre-transcoded HLS cache on the NAS for instant remote. |
| Subtitles | The desktop/mobile player (libmpv) renders embedded MKV subs β PGS, ASS, SRT β natively with direct play, no burn-in. Dumb clients get pre-extracted WebVTT sidecars; bitmap subs are OCR'd to text once and cached. Burn-in is the last resort, never the default. |
| Remote access (the Plex paywall) | Rides your own reverse proxy (e.g. Nginx Proxy Manager + CloudFlare) β HTTPS remote with no subscription, no relay, no phone-home. Expiring scoped share links to a single title. Per-user bandwidth caps. |
| UI / apps | One Flutter codebase β Windows, Android, Android/Fire TV 10-foot shell, iOS. Living-room control via phone-as-remote β PC renderer. Blurred-backdrop detail pages, global resume bar, Cast. |
Two rules sit above every feature in this repo:
NASCinema is built for many users on one server, not one person's setup. Therefore:
- No host, path, port, credential, API key, or device name is ever baked into the code. Every one of them is configuration.
- Server-level settings (media library paths,
DATABASE_URL, integration keys, public URL) live in.env/ an admin settings UI β set once per install. - Per-user settings (preferred language, subtitle style & defaults, bandwidth cap, renderer devices, Trakt account, content-rating limits, theme) live in that user's account and travel with them across devices.
- A clean separation: admin configures the server; each user configures their own experience. If you ever feel tempted to type a literal hostname or path into a source file, it belongs in config instead.
- We start pre-v1.0 and stay there until NASCinema is genuinely remarkable.
v1.0.0is a milestone, not a default. - Version numbers are reserved for real, shipped features or critical bug fixes. One-line fixes, refactors, doc tweaks, and chores do not bump the version.
- Semantic-ish within 0.x:
0.MINOR.0for a meaningful feature set,0.x.PATCHonly for critical fixes. Everyday commits just land onmainwith no version change.
NASCinema is unmistakably part of the NAS family β same deep-navy cyberpunk base and frosted-glass grammar as NASRadio β but with its own "Blockbuster cyberpunk" accent so you know at a glance you're in the movies app, not the music app.
| Token | Value | Use |
|---|---|---|
| Background | #0a0e27 |
App canvas (shared with the family) |
| Surface | #141a35 |
Cards, sheets |
| Surface raised | #1d2547 |
Posters, inputs |
| Accent (primary) | #ffb020 |
Marquee amber β play buttons, active nav, badges |
| Accent (secondary) | #b46bff |
Violet β avatars, TV badges, highlights |
| Text | #eef1ff |
Primary text |
| Muted | #8b93bf |
Secondary text |
Frosted-glass cards Β· blurred backdrop ambient glow Β· color-coded format badges (4K / HDR / 1080p) Β· global mini/resume player Β· 10-foot TV layout Β· A-Z quick scroll.
Flutter Frontend (Windows / Android / Fire TV / iOS)
+-- media_kit (libmpv) β desktop & TV player: direct play, native MKV subs, HDR
+-- video_player / native β mobile playback
+-- http (REST API with retry helpers)
+-- socket_io_client (scan progress, phone-as-remote, Watch Together)
|
| HTTPS + WSS via Nginx Proxy Manager + CloudFlare
v
FastAPI Backend (Native Windows / Synology NAS)
+-- async streaming (HTTP range, on-the-fly HLS segments)
+-- PostgreSQL 17 (library, users, watch state, sub/codec probe cache)
+-- itsdangerous signed tokens + scrypt hashing (ported from NASRadio)
+-- Scoped read-only media tokens (stream/artwork/share links)
+-- FFmpeg / FFprobe (probe, remux, transcode, HLS, trickplay, sub extract)
+-- Subtitle pipeline (VTT extract, PGS/VOBSUB OCR, OpenSubtitles)
+-- TMDB / Fanart / Trakt / OpenSubtitles integrations
+-- Radarr / Sonarr / Prowlarr / Transmission (acquisition, self-healing)
+-- \\<your-nas>\<your-share> media β configured per install, never hardcoded (SMB/NFS)
Sidecar services (optional, NAS-hosted like NASRadio): PGS/VOBSUB OCR worker and a batch transcode/trickplay worker.
| Component | Technology |
|---|---|
| Language | Python 3.14 (core backend; ML/audio sidecars may pin an older interpreter) |
| Framework | FastAPI (async) + Uvicorn |
| Database | PostgreSQL 17 |
| Auth | scrypt hashing + itsdangerous signed tokens (ported from NASRadio) |
| Media probe / transcode | FFmpeg + FFprobe |
| Subtitle OCR | Tesseract / pgsrip (bitmap β SRT) |
| Realtime | python-socketio (ASGI) |
| Metadata | TMDB, Fanart.tv, OMDb |
| Watch sync | Trakt |
| Subtitles | OpenSubtitles |
| Acquisition | Radarr, Sonarr, Prowlarr, Transmission |
| Component | Technology |
|---|---|
| Framework | Flutter (latest stable) |
| Language | Dart |
| Desktop / TV player | media_kit (libmpv) |
| Mobile player | video_player / platform native |
| HTTP | http |
| Realtime | socket_io_client |
| State | Provider + ChangeNotifier |
| Token storage | flutter_secure_storage |
| Cast | bonsoir (mDNS) + custom receiver |
| Component | Technology |
|---|---|
| Runtime | Native Windows or Linux (FastAPI/Uvicorn) |
| Database | PostgreSQL 17 |
| Storage | Any SMB/NFS-accessible NAS (Synology, TrueNAS, Unraid, β¦) |
| Reverse proxy | Any (e.g. Nginx Proxy Manager, Caddy, Traefik) |
| DNS | Any (e.g. CloudFlare) |
All infrastructure rows are examples, not requirements β every host, path, port, and key is set in config, never baked into the code.
Subtitles are NASCinema's flagship differentiator, because they're where Plex/JF hurt the most. There are two fundamentally different kinds:
- Text subs β SRT, ASS/SSA, mov_text. Strings + timecodes.
- Bitmap subs β PGS (Blu-ray
.sup), VOBSUB (DVD). Pictures of text, with no text data.
Browsers and locked-down TV clients can only render WebVTT (text). So when Plex/JF meet a PGS track, their only fallback is to burn it into the video β forcing a full transcode and breaking direct play. NASCinema avoids that:
- Probe every track at scan (ffprobe) β codec, language, forced/default flags stored per file.
- Native render where it counts β the libmpv-based player on PC/phone draws PGS, ASS, and SRT as overlays without re-encoding video. Direct play stays intact.
- Pre-process for dumb clients, cached on the NAS β text subs β WebVTT sidecar (instant); bitmap subs β OCR to SRT once, then treated as text forever.
- Burn-in is the last resort, only when a client can do nothing else β and the player tells you why.
Plus: OpenSubtitles auto-download, per-file sync offset (remembered), forced/default track selection.
Often the best video player you own is a PC already wired to the TV. Many smart-TV platforms (e.g. webOS, Roku) aren't sideload-friendly, so NASCinema flips the problem:
Browse on your phone β tap "Play on <renderer>" β it plays on the big screen. Phone is the remote, the TV-connected PC is the renderer β Spotify-Connect style, over the same socket channel NASRadio uses for device handoff.
Renderers are discovered, named, and saved per user β no device is hardcoded. Best-in-class playback (direct play, native subs, HDR) with zero sideloading. Fallbacks for when the PC is off:
- TV-browser web app β open your NASCinema URL right in the smart-TV browser (HLS + VTT sidecars).
- Roku channel (later) β a thin HLS client.
- webOS native app (much later) β packaged Flutter web build via Dev Mode.
β³ Coming with Milestone 1. Will mirror NASRadio's flow: configure
.env(media paths,DATABASE_URL, TMDB key), create the first admin user,python run.py --scan, then point the Flutter app at your server from the login screen's gear icon. See ROADMAP.md for current status.
The full phased plan with checkboxes is in ROADMAP.md. The short version:
- Foundation β repo, theme, stack decisions β we are here
- Movies MVP β scan β TMDB β browse β direct-play/remux/transcode + "why" badge β remote
- Subtitles done right β probe, native render, VTT/OCR sidecars, sync offset
- The living room β phone-as-remote β PC renderer, Cast, C2 web app
- TV shows β series/season/episode, watched state, next-episode autoplay, On Deck
- Discovery & personalization β recommendations, smart collections, trickplay, Trakt, Year in Review
- Sharing & multi-user β profiles, kids, share links, request system, Watch Together
- Acquisition β Radarr / Sonarr / Prowlarr / Transmission
- The cinema experience β Coming Attractions, Roulette, Art Mode, "Because it's raining," premieres
- Polish, ops & more clients β transcode dashboard, health, auto-update, Roku, webOS
MIT β see LICENSE.
Matt Hanington β part of the NAS family (NASRadio)