3d5de06b44
AI-powered web app that recommends unwatched movies from a Jellyfin library based on natural language mood input. Jellyfin auth, modular LLM backend (Claude/OpenAI/Ollama), two-tier pre-filter + AI ranking, mobile-responsive dark theme UI with poster cards and deep links. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
4.3 KiB
4.3 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
"Movie Night" — an AI-powered media discovery web app for a household with ~53TB of movies on a self-hosted Jellyfin media server. Users describe their mood ("pizza night with the kids", "light fun movie after a hard week") and get AI-ranked recommendations from their unwatched library, with posters and one-click deep links to Jellyfin.
Users: Kenny and his wife — the primary use case is "what should we watch tonight?"
Architecture
Frontend (Tailwind + vanilla JS) → FastAPI backend → LLM (Claude/OpenAI/Ollama)
↓
Jellyfin API → SQLite cache
- Backend: Python 3.12 + FastAPI (app/)
- Frontend: Single-page HTML/CSS/JS with Tailwind CDN (app/static/)
- LLM: Modular provider — Anthropic Claude, OpenAI, or Ollama (app/services/llm/)
- Auth: Jellyfin credentials (like Jellyseerr) — no separate user database
- Data: SQLite cache of movie metadata synced from Jellyfin, persisted at /data/library.db
- Deployment: Docker Compose on bondelie-media (192.168.5.254), port 5210
Key Files
app/main.py— FastAPI app entry, lifespan (DB init + background sync), poster proxy, static mountapp/config.py— Settings via pydantic-settings (env vars)app/database.py— SQLite schema and query helpersapp/routers/auth.py— Jellyfin-based login/logout/session managementapp/routers/mood.py— POST /api/mood — the core recommendation flowapp/services/jellyfin.py— Jellyfin API client (auth, library fetch, watch state, posters)app/services/prefilter.py— Tier-1 keyword/genre scoring to narrow candidates before LLMapp/services/llm/— Modular LLM providers (anthropic, openai, ollama)app/services/library_sync.py— Background sync of movie metadata + watch stateapp/static/index.html— Frontend: login, mood input, result cardsapp/static/app.js— Frontend logic
Related Infrastructure
Integrates with the media server documented in ../media-server-migration/:
| Service | Port | Purpose |
|---|---|---|
| Jellyfin | 8096 | Library metadata, watch state, posters |
| Movie Night | 5210 | This app |
Server: bondelie-media (192.168.5.254) — Ubuntu 24.04 LTS
Proxy: movienight.internal.bondelie.net
Recommendation Flow
- User types mood text
- Backend fetches unwatched movies from SQLite cache (filtered by selected users' watch state)
prefilter.pyscores movies by genre/keyword match, returns top 200 candidates- LLM provider ranks candidates and returns top 6 with reasoning (structured JSON)
- Backend validates jellyfin_ids, enriches with poster URLs and deep links
- Frontend renders movie cards with posters, metadata, reasoning, and "Watch" button
Secrets
Store in .env (gitignored), never commit:
JELLYFIN_API_KEY— Jellyfin API key (Settings → API Keys)LLM_API_KEY— Anthropic/OpenAI API key (not needed for Ollama)SESSION_SECRET— Random string for cookie signing
Development Commands
# Local development (requires Python 3.12+)
pip install -r requirements.txt
cp .env.example .env # then fill in API keys
uvicorn app.main:app --reload --port 5210
# Docker build and run
docker compose up --build
# Deploy to bondelie-media
scp -r . kenny@192.168.5.254:/srv/stacks/movie-night/
ssh kenny@192.168.5.254 "cd /srv/stacks/movie-night && docker compose up --build -d"
Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
| JELLYFIN_URL | Yes | http://192.168.5.254:8096 | Jellyfin server URL |
| JELLYFIN_API_KEY | Yes | — | Jellyfin API key |
| JELLYFIN_EXTERNAL_URL | Yes | https://jellyfin.internal.bondelie.net | URL for deep links |
| LLM_PROVIDER | No | anthropic | anthropic, openai, or ollama |
| LLM_API_KEY | Yes* | — | API key (*not needed for ollama) |
| LLM_MODEL | No | varies by provider | Model name override |
| LLM_BASE_URL | No | — | Base URL override (for ollama) |
| SESSION_SECRET | Yes | — | Random string for sessions |