levanter-dev/Diwan-Media-Server

0

stars

0

commits

Python

primary language

Aug 23, 2026

updated

README

Diwan

A self-hosted Windows media library and web portal for movies, series, and audio. Browse your local folders, discover new titles via TMDB/OMDb, search and download subtitles, and stream to any device on your local network. Built-in AI automatically detects and filters NSFW and inappropriate scenes.

Features

  • Local media library - Scan Windows drives and folders; organize movies, series, and audio
  • Explore catalogue - Search TMDB and OMDb for movies/series, browse trending and popular rows
  • Personal scoring - Rate titles 1-10 to build a taste profile for recommendations
  • In-page video player - Play media directly with subtitle overlay, seek controls, and progress saving
  • Subtitle search - Find and download subtitles from OpenSubtitles, with language filter
  • AI content filtering - Automatically detect and skip/warn/mark NSFW, nudity, and inappropriate scenes using on-device AI (no cloud)
  • Multi-device - Access from any device on your local network (phone, tablet, TV)
  • Circle scoring - Multiple household members can maintain independent scores

Quick Start

Prerequisites

  • Windows 10 or 11
  • Python 3.12+ - python.org
  • Node.js 20+ - nodejs.org
  • FFmpeg (for the packaged build) - place ffmpeg.exe and ffprobe.exe in vendor/ffmpeg/bin/

1. Clone and configure

git clone https://github.com/levanter-dev/Diwan-Media-Server.git
cd Diwan-Media-Server
copy .env.example .env

Edit .env and add your API keys (see API Keys below). At minimum you will want a TMDB token for the Explore page.

2. Install and run

scripts\01-setup.bat
scripts\02-run-development.bat

Open http://localhost:8080 in your browser.

3. Access from other devices

On other devices, open http://<your-pc-ip>:8080 (e.g. http://192.168.1.50:8080).

To use a friendly name instead of an IP, set DOMAIN=diwan.local in your .env file. The server automatically advertises the name via mDNS (Bonjour/Zeroconf) so any device on your network can reach it at http://diwan.local:8080 - no hosts file editing or router config required.

Use the :8080 form unless the server reports that it successfully opened port 80. Some webOS versions do not resolve mDNS names; in that case use the displayed LAN IP or add diwan.local to your router's local DNS. A name that does not end in .local always needs a router/DNS/hosts record.

Press Ctrl+C to stop both servers.

Architecture

Browser / TV / Phone
    |
    | http://server-pc:8080
    v
Node portal (serves web UI)
    |
    | http://127.0.0.1:8081/api
    v
Python FastAPI server
    |-- Windows drives and folders
    |-- SQLite database
    |-- FFmpeg / FFprobe
    |-- Media scanning and jobs

Development uses two processes: Python FastAPI (port 8081) and Node.js (port 8080). The packaged .exe combines both into a single process.

API Keys

Diwan uses three external services. All are free.

TMDB (The Movie Database)

Used for search, posters, metadata, and discovery rows (trending, popular, now playing).

  1. Sign up at themoviedb.org
  2. Go to Settings -> API
  3. Request an API key - choose Developer
  4. Copy the API Read Access Token (v4 auth) - starts with eyJ...
  5. Add to .env: TMDB_TOKEN=eyJ...

This product uses the TMDB API but is not endorsed or certified by TMDB.

OMDb (Open Movie Database)

Alternative search engine.

  1. Go to omdbapi.com/apikey.aspx
  2. Choose FREE tier (1,000 requests/day)
  3. Enter your email; you will receive a key
  4. Add to .env: OMDB_API_KEY=your-key

OpenSubtitles

Subtitle search and download.

  1. Sign up at opensubtitles.com
  2. Go to Consumers
  3. Create a new Consumer (any name)
  4. Copy the API Key
  5. Add to .env:
    OPENSUBTITLES_USERNAME=your-username
    OPENSUBTITLES_PASSWORD=your-password
    OPENSUBTITLES_API_KEY=your-api-key
    

Credentials are stored in SQLite. The browser only sees configured/not configured status.

AI Content Filtering

Diwan uses on-device AI to detect and handle inappropriate scenes - no files are uploaded to any cloud service. Two AI models run locally:

Detected categories

CategoryDetectorDescription
Sexual activityOpenCLIPExplicit sexual content in a scene
Female toplessnessNudeNetExposed female chest
Male toplessnessNudeNetExposed male chest
General nudityNudeNetAny exposed private parts or buttocks
KissingOpenCLIPRomantic kissing scenes
Revealing attire / swimwearOpenCLIPScantily clad or swimwear scenes

Per-category actions

Each category can be independently set to one of four modes:

ActionBehavior
OffCategory is ignored
MarkerShows colored markers on the playback timeline
WarnDisplays a warning overlay before the scene; viewer can choose to skip or continue
SkipAutomatically jumps past the detected scene with a brief notice

Sensitivity

Three sensitivity levels control the detection threshold: Low (fewer false positives), Balanced, and High (catches more borderline scenes).

Model selection

Settings can choose the global analysis model, and each media details page can override that model before re-filtering a title.

ModelBest use
NudeNet + OpenCLIPBroadest coverage: anatomy plus scene context
NudeNet onlyMore predictable body-part detection with fewer context false positives
NudeNet strict private-partsHighest precision for generic nudity; breasts stay separate from private-parts nudity
OpenCLIP prompts onlyExperimental markers for scene context; avoid for automatic skips
GantMan NSFW modelOptional broad NSFW classifier; requires nsfw_detector, TensorFlow, and NSFW_MODEL_PATH
HaramBlur-style NSFWJSServer-side NSFWJS/GantMan-style classifier profile; HaramBlur itself also uses browser face detection through Human.js
FalconsAI NSFW ViTStrong independent binary NSFW classifier; downloads from Hugging Face on first use
FalconsAI NSFW ViT 2026Newer gated classifier; accept its model terms and authenticate with HF_TOKEN or hf auth login

Confirmation pipeline

The confirmation sandbox can require multiple model profiles to agree within a time window before a detected segment is used for automatic skipping. A single model can still pass if its confidence is above the configured high-confidence threshold. Media pages can also run all installed model profiles, compare raw segments by model, select any completed combination to drive playback, and manually verify an exact time range as blocked or safe. Selected model timelines are combined before the confirmation rule is applied.

The media filter card also includes a guided unsafe-scene review. It plays only the detected ranges in chronological order, pauses at each scene boundary, and records a Safe or Not safe verdict with an exact scene number. Review analytics group selected-model detections into confidence bands and show the observed false-positive rate for each band and model; unreviewed detections are excluded.

Filter export/import uses the v2 format and includes the global confirmation settings, per-media model overrides, raw per-model timelines, and manual verification ranges.

Detection is intentionally precision-first for automatic skips and requires corroborating frames. No visual classifier can promise 100% accuracy, so detected scenes can be marked safe and automatic skipping can be replaced with warnings or timeline markers for categories where false positives are unacceptable.

Portable filters

Settings can export a JSON filter file containing the policy and all prepared scene timelines. Importing it on another Diwan server matches media by filename and size, allowing a fast computer to do the analysis for a lower-powered one.

GPU acceleration

Content analysis runs significantly faster with an NVIDIA GPU (CUDA). On CPU-only systems, analysis still works but takes longer. The Settings page shows whether GPU acceleration is available.

How it works

  1. Enable content filtering in Settings and choose your preferred actions
  2. Newly scanned videos are analyzed automatically (or trigger manually)
  3. During playback, detected scenes are handled according to your settings
  4. You can override filters per media item from the media details page

During playback, unsupported containers/codecs are converted to an H.264/AAC compatibility stream for webOS and other TVs. This is server-side playback compatibility; the custom controls alone cannot add codec support to a TV.

Configuration

All settings go in .env. See .env.example for the full list.

VariableDefaultDescription
TMDB_TOKEN-TMDB API Read Access Token
OMDB_API_KEY-OMDb API key
OPENSUBTITLES_USERNAME-OpenSubtitles username
OPENSUBTITLES_PASSWORD-OpenSubtitles password
OPENSUBTITLES_API_KEY-OpenSubtitles consumer API key
PORT8080Web portal port
DOMAIN-Custom local domain (e.g. diwan.local)
MEDIA_ROOTSAll drivesComma-separated folder paths
DATA_DIRAppDataDatabase and config location

Building the Windows Executable

To create a standalone .exe:

  1. Place ffmpeg.exe and ffprobe.exe in vendor/ffmpeg/bin/
  2. Run:
scripts\03-build-exe.bat
scripts\04-install-local.bat
scripts\05-run-installed.bat

The server auto-starts at logon. To remove:

scripts\90-uninstall-local.bat

Project Structure

app/                    FastAPI backend (database, scanner, scrapers, analysis)
web/                    Browser SPA + Node dev server
scripts/                Setup, run, build, install, uninstall
vendor/ffmpeg/          Place ffmpeg.exe + ffprobe.exe here
native_server.py        Entry point for the native Windows executable
native_server.spec      PyInstaller packaging definition
requirements.txt        Python dependencies
requirements-build.txt  Build dependencies
.env.example            Config template

Security

  • Media files stay on the server. Nothing is uploaded.
  • API keys and passwords live in server-side SQLite, never sent to the browser.
  • Do not expose port 8080 to the public internet.
  • Authentication is planned for a future release.

License

MIT

levanter-dev/Diwan-Media-Server

0

stars

0

commits

Python

primary language

Aug 23, 2026

updated

README

Diwan

A self-hosted Windows media library and web portal for movies, series, and audio. Browse your local folders, discover new titles via TMDB/OMDb, search and download subtitles, and stream to any device on your local network. Built-in AI automatically detects and filters NSFW and inappropriate scenes.

Features

  • Local media library - Scan Windows drives and folders; organize movies, series, and audio
  • Explore catalogue - Search TMDB and OMDb for movies/series, browse trending and popular rows
  • Personal scoring - Rate titles 1-10 to build a taste profile for recommendations
  • In-page video player - Play media directly with subtitle overlay, seek controls, and progress saving
  • Subtitle search - Find and download subtitles from OpenSubtitles, with language filter
  • AI content filtering - Automatically detect and skip/warn/mark NSFW, nudity, and inappropriate scenes using on-device AI (no cloud)
  • Multi-device - Access from any device on your local network (phone, tablet, TV)
  • Circle scoring - Multiple household members can maintain independent scores

Quick Start

Prerequisites

  • Windows 10 or 11
  • Python 3.12+ - python.org
  • Node.js 20+ - nodejs.org
  • FFmpeg (for the packaged build) - place ffmpeg.exe and ffprobe.exe in vendor/ffmpeg/bin/

1. Clone and configure

git clone https://github.com/levanter-dev/Diwan-Media-Server.git
cd Diwan-Media-Server
copy .env.example .env

Edit .env and add your API keys (see API Keys below). At minimum you will want a TMDB token for the Explore page.

2. Install and run

scripts\01-setup.bat
scripts\02-run-development.bat

Open http://localhost:8080 in your browser.

3. Access from other devices

On other devices, open http://<your-pc-ip>:8080 (e.g. http://192.168.1.50:8080).

To use a friendly name instead of an IP, set DOMAIN=diwan.local in your .env file. The server automatically advertises the name via mDNS (Bonjour/Zeroconf) so any device on your network can reach it at http://diwan.local:8080 - no hosts file editing or router config required.

Use the :8080 form unless the server reports that it successfully opened port 80. Some webOS versions do not resolve mDNS names; in that case use the displayed LAN IP or add diwan.local to your router's local DNS. A name that does not end in .local always needs a router/DNS/hosts record.

Press Ctrl+C to stop both servers.

Architecture

Browser / TV / Phone
    |
    | http://server-pc:8080
    v
Node portal (serves web UI)
    |
    | http://127.0.0.1:8081/api
    v
Python FastAPI server
    |-- Windows drives and folders
    |-- SQLite database
    |-- FFmpeg / FFprobe
    |-- Media scanning and jobs

Development uses two processes: Python FastAPI (port 8081) and Node.js (port 8080). The packaged .exe combines both into a single process.

API Keys

Diwan uses three external services. All are free.

TMDB (The Movie Database)

Used for search, posters, metadata, and discovery rows (trending, popular, now playing).

  1. Sign up at themoviedb.org
  2. Go to Settings -> API
  3. Request an API key - choose Developer
  4. Copy the API Read Access Token (v4 auth) - starts with eyJ...
  5. Add to .env: TMDB_TOKEN=eyJ...

This product uses the TMDB API but is not endorsed or certified by TMDB.

OMDb (Open Movie Database)

Alternative search engine.

  1. Go to omdbapi.com/apikey.aspx
  2. Choose FREE tier (1,000 requests/day)
  3. Enter your email; you will receive a key
  4. Add to .env: OMDB_API_KEY=your-key

OpenSubtitles

Subtitle search and download.

  1. Sign up at opensubtitles.com
  2. Go to Consumers
  3. Create a new Consumer (any name)
  4. Copy the API Key
  5. Add to .env:
    OPENSUBTITLES_USERNAME=your-username
    OPENSUBTITLES_PASSWORD=your-password
    OPENSUBTITLES_API_KEY=your-api-key
    

Credentials are stored in SQLite. The browser only sees configured/not configured status.

AI Content Filtering

Diwan uses on-device AI to detect and handle inappropriate scenes - no files are uploaded to any cloud service. Two AI models run locally:

Detected categories

CategoryDetectorDescription
Sexual activityOpenCLIPExplicit sexual content in a scene
Female toplessnessNudeNetExposed female chest
Male toplessnessNudeNetExposed male chest
General nudityNudeNetAny exposed private parts or buttocks
KissingOpenCLIPRomantic kissing scenes
Revealing attire / swimwearOpenCLIPScantily clad or swimwear scenes

Per-category actions

Each category can be independently set to one of four modes:

ActionBehavior
OffCategory is ignored
MarkerShows colored markers on the playback timeline
WarnDisplays a warning overlay before the scene; viewer can choose to skip or continue
SkipAutomatically jumps past the detected scene with a brief notice

Sensitivity

Three sensitivity levels control the detection threshold: Low (fewer false positives), Balanced, and High (catches more borderline scenes).

Model selection

Settings can choose the global analysis model, and each media details page can override that model before re-filtering a title.

ModelBest use
NudeNet + OpenCLIPBroadest coverage: anatomy plus scene context
NudeNet onlyMore predictable body-part detection with fewer context false positives
NudeNet strict private-partsHighest precision for generic nudity; breasts stay separate from private-parts nudity
OpenCLIP prompts onlyExperimental markers for scene context; avoid for automatic skips
GantMan NSFW modelOptional broad NSFW classifier; requires nsfw_detector, TensorFlow, and NSFW_MODEL_PATH
HaramBlur-style NSFWJSServer-side NSFWJS/GantMan-style classifier profile; HaramBlur itself also uses browser face detection through Human.js
FalconsAI NSFW ViTStrong independent binary NSFW classifier; downloads from Hugging Face on first use
FalconsAI NSFW ViT 2026Newer gated classifier; accept its model terms and authenticate with HF_TOKEN or hf auth login

Confirmation pipeline

The confirmation sandbox can require multiple model profiles to agree within a time window before a detected segment is used for automatic skipping. A single model can still pass if its confidence is above the configured high-confidence threshold. Media pages can also run all installed model profiles, compare raw segments by model, select any completed combination to drive playback, and manually verify an exact time range as blocked or safe. Selected model timelines are combined before the confirmation rule is applied.

The media filter card also includes a guided unsafe-scene review. It plays only the detected ranges in chronological order, pauses at each scene boundary, and records a Safe or Not safe verdict with an exact scene number. Review analytics group selected-model detections into confidence bands and show the observed false-positive rate for each band and model; unreviewed detections are excluded.

Filter export/import uses the v2 format and includes the global confirmation settings, per-media model overrides, raw per-model timelines, and manual verification ranges.

Detection is intentionally precision-first for automatic skips and requires corroborating frames. No visual classifier can promise 100% accuracy, so detected scenes can be marked safe and automatic skipping can be replaced with warnings or timeline markers for categories where false positives are unacceptable.

Portable filters

Settings can export a JSON filter file containing the policy and all prepared scene timelines. Importing it on another Diwan server matches media by filename and size, allowing a fast computer to do the analysis for a lower-powered one.

GPU acceleration

Content analysis runs significantly faster with an NVIDIA GPU (CUDA). On CPU-only systems, analysis still works but takes longer. The Settings page shows whether GPU acceleration is available.

How it works

  1. Enable content filtering in Settings and choose your preferred actions
  2. Newly scanned videos are analyzed automatically (or trigger manually)
  3. During playback, detected scenes are handled according to your settings
  4. You can override filters per media item from the media details page

During playback, unsupported containers/codecs are converted to an H.264/AAC compatibility stream for webOS and other TVs. This is server-side playback compatibility; the custom controls alone cannot add codec support to a TV.

Configuration

All settings go in .env. See .env.example for the full list.

VariableDefaultDescription
TMDB_TOKEN-TMDB API Read Access Token
OMDB_API_KEY-OMDb API key
OPENSUBTITLES_USERNAME-OpenSubtitles username
OPENSUBTITLES_PASSWORD-OpenSubtitles password
OPENSUBTITLES_API_KEY-OpenSubtitles consumer API key
PORT8080Web portal port
DOMAIN-Custom local domain (e.g. diwan.local)
MEDIA_ROOTSAll drivesComma-separated folder paths
DATA_DIRAppDataDatabase and config location

Building the Windows Executable

To create a standalone .exe:

  1. Place ffmpeg.exe and ffprobe.exe in vendor/ffmpeg/bin/
  2. Run:
scripts\03-build-exe.bat
scripts\04-install-local.bat
scripts\05-run-installed.bat

The server auto-starts at logon. To remove:

scripts\90-uninstall-local.bat

Project Structure

app/                    FastAPI backend (database, scanner, scrapers, analysis)
web/                    Browser SPA + Node dev server
scripts/                Setup, run, build, install, uninstall
vendor/ffmpeg/          Place ffmpeg.exe + ffprobe.exe here
native_server.py        Entry point for the native Windows executable
native_server.spec      PyInstaller packaging definition
requirements.txt        Python dependencies
requirements-build.txt  Build dependencies
.env.example            Config template

Security

  • Media files stay on the server. Nothing is uploaded.
  • API keys and passwords live in server-side SQLite, never sent to the browser.
  • Do not expose port 8080 to the public internet.
  • Authentication is planned for a future release.

License

MIT

Languages

Python

49.7%

HTML

39.7%

JavaScript

7.6%

CSS

2.0%