Note
Answer "What should I watch tonight?" based on how you feel, not just by genre.
Imagine having 500+ movies in Jellyfin and spending 30 minutes every night scrolling through them. Genre filters didn't help because "Thriller" includes everything from The Bourne Identity to Hereditary.
I wanted to browse by mood, not just category. So I built this.
Nocturne is a vibe-based movie discovery layer that sits alongside Jellyfin, organizing your library by each film's atmosphere and tone instead of generic genres. It powers a dedicated viewer app and includes a web UI for setup and tag management.
The Problem: Browsing by genre doesn't match how you actually choose movies. "Action" doesn't tell you if it's a brainless popcorn flick or a slow-burn thriller.
The Solution: Movie's mood-based organization. Instead of "Thriller," you browse:
- π Dialogue-Driven - Character-focused narratives
- π§οΈ Rain & Neon Aesthetic - Cyberpunk/neo-noir vibes
- π§ Brainmelt Zone - Mind-bending psychological films
- π Ha Ha Ha - Pure comedy, no drama
36 curated moods (expandable) that actually answer "what matches my vibe right now?"
flowchart TB
Jellyfin[("Jellyfin<br/>source of truth<br/>catalog Β· playback Β· watched")]
subgraph Nocturne["Nocturne β vibe index layer"]
direction LR
API["Hummingbird API<br/>:3242"]
WS["Jellyfin Realtime<br/>WebSocket listener"]
SuggQ["Claude suggestion<br/>queue"]
OMDbProxy["OMDb proxy<br/>(15-day cache)"]
WebUI["Web UI<br/>setup Β· tagging Β· approvals"]
DB[("SQLite<br/>jellyfinId β mood tags")]
WebUI --- API
API --- DB
WS --- DB
SuggQ --- DB
OMDbProxy --- DB
end
Client["Client<br/>(viewer apps)"]
Claude["Claude API<br/>(BYOK)"]
OMDb["OMDb API<br/>(BYOK)"]
Jellyfin <-- "REST + WebSocket" --> WS
Client -- "streams Β· catalog Β· watched" --> Jellyfin
Client -- "mood queries Β· tag reads" --> API
SuggQ -- "mood suggestions" --> Claude
OMDbProxy -- "ratings lookup" --> OMDb
Important
Jellyfin is the source of truth. Nocturne is a pure vibe-index layer β it stores only
jellyfinId β mood tags and never mirrors Jellyfin metadata. Clients talk to
Jellyfin directly for catalog/playback and to Nocturne for mood curation.
How it works:
- Nocturne syncs your Jellyfin library by ID only (one-time setup via Web UI, then real-time WebSocket reconciliation)
- New movies auto-queue a Claude mood-tag suggestion (hybrid approval flow)
- Admin reviews suggestions in the Web UI and approves/rejects before tags are written
- Clients join their Jellyfin data with Nocturne's mood tags on the fly
- β Mood-based organization β 36 curated moods (see full list below)
- β Real-time sync β Jellyfin WebSocket listener keeps the ID set fresh
- β Hybrid auto-tagging β Claude queues mood suggestions, admin approves
- β Manual tagging β Web UI for fine-tuning
- β Import/Export β Backup your mood mappings
- β External ratings β OMDb proxy for IMDb/RT/Metacritic (BYOK, shared 15-day cache)
- β Built-in Web UI β No separate admin tools needed
- β Slim by design β stores only Jellyfin IDs + tags; no mirrored metadata
- π Local-first - All data in
./data/nocturne.sqlite - π No telemetry - Zero tracking, no accounts
- π BYOK only - AI tagging requires your own API key (optional)
docker run -d --name nocturne-server -p 3242:3242 \
-v "$(pwd)/data:/app/data" \
-v "$(pwd)/config:/app/config" \
-v "$(pwd)/logs:/app/logs" \
-e WEBUI_PORT=3242 \
-e NOCTURNE_DATABASE_PATH=/app/data/nocturne.sqlite \
--restart unless-stopped \
ghcr.io/imgkl/nocturneserver:latestThen:
- Open
http://localhost:3242in your browser (Web UI) - Complete setup wizard (enter Jellyfin URL + credentials)
- Run initial sync
git clone https://github.com/imgkl/NocturneServer
cd NocturneServer
./run.shWeb UI: http://localhost:8001
Click to expand full mood list
- Dialogue-Driven
- Vibe Is the Plot
- Existential Core
- Antihero Study
- Ensemble Mosaic
- Crime, Grit & Style
- Men With Vibes (and Guns)
- Obsidian Noir
- Rain & Neon Aesthetic
- Cat and Mouse
- Brainmelt Zone
- The Twist Is the Plot
- Psychological Pressure-Cooker
- Late-Night Mind Rattle
- Uncanny Vibes
- Slow Burn, Sharp Blade
- One-Room Pressure Cooker
- Visual Worship
- Rainy Day Rewinds
- Quiet Epics
- Emotional Gut Punch
- Feel-Good Romance
- Coming of Age
- Bittersweet Aftermath
- Ha Ha Ha (Comedy)
- Horror & Unease
- Heist Energy
- Time Twists
- Film School Shelf
- Modern Masterpieces
- Regional Gems
- Underseen Treasures
- Based on Vibes (True Story)
- Cult Chaos
- Experimental Cinema
- WTF Did I Watch
Access at http://your-server:3242
- Setup Wizard - Initial Jellyfin connection configuration
- Sync Management - Manual sync, view sync status and history
- Movie Browser - View all movies with their current mood tags
- Tag Editor - Add/remove mood tags from individual movies
- AI Auto-Tag - Bulk suggestions for untagged movies (requires Claude API key)
- Import/Export - Backup and restore your mood mappings
- Settings - Configure integrations (OMDB, Claude API)
- Jellyfin integration
- Mood tagging system
- Real-time webhook sync
- Web UI for admin tasks
- AI auto-tagging (BYOK: Claude)
- Import/Export mood mappings
- OMDB ratings integration (BYOK)
- User-defined custom moods
- Multi-user support
- Advanced filtering (combine moods, exclude tags)
- Mood analytics (most-watched moods, etc.)
This is a personal project, but PRs are welcome for:
- Bug fixes
- New mood definitions (with clear criteria)
- Performance improvements
- Web UI enhancements
Please open an issue first to discuss major changes.
MIT - Use it, fork it, modify it. Just don't blame me if your movie night goes wrong.