A self-hosted media recommendation engine. It's not bad, it's just... mediocre.
Mediocre is a fast, self-hosted recommendation system that learns what you like from your watch history and suggests what to watch next. Cross-domain (movies, TV, anime), served from SQLite in under 50ms.
Because every recommendation app promises to change your life. This one just gives you solid suggestions and gets out of your way. No gamification, no social feeds, no dark patterns. Just... adequate recommendations. π
The For You tab β personalized picks with a Top Pick hero, match scores, and quick actions (Request / Mark watched)
- Learns your taste from watch history + explicit ratings (1β10)
- 3-Layer Ensemble Scoring Engine:
- Content Filtering (50%) β Genre & keyword matching weighted by your ratings
- Collaborative Filtering (30%) β User similarity from watch history overlap
- Popularity/Recency (20%) β TMDB popularity Γ release year decay
- Keyword-aware recommendations β Syncs TMDB keywords for finer-grained taste profiles
- Feedback loop β Dismiss items you don't want, rate what you've watched, profile rebuilds instantly
- Auto-detects anime β Genre heuristics + origin language detection
- Fuzzy title matching β Levenshtein distance resolver for catalog mapping
- Rich web UI β 6 tabs (For You, Movies, TV, Anime, Trending, Hidden Gems), themable accents, localStorage persistence
- Import from anywhere:
- Plex β Watch history import with library scanning
- AniList β Anime list sync (improves anime recommendations significantly)
- Trakt β Movie/TV watch history & ratings sync
- Overseerr/Seerr integration β Request media directly to your Overseerr or Seerr instance from Mediocre's UI
| Component | Technology |
|---|---|
| Backend | Node.js + Express (ESM) |
| Database | SQLite via better-sqlite3 (WAL mode, 64MB cache) |
| Frontend | React 18 + Vite |
| Catalog | TMDB API (synced locally, no API calls on hot path) |
| Media Server | Plex (Jellyfin not supported β I don't run one to test it) |
| External Lists | AniList API, Trakt API |
| Request Proxying | Overseerr / Seerr API |
All scoring is pre-computed or cached in SQLite. User requests never hit external APIs:
- Response time: <50ms (measured 18β40ms on test hardware)
- Full-text search: FTS5 enabled
- Schema: 23+ tables with migration system
- Node.js β₯18
- A TMDB API key (free)
- A Plex server with content
- (Optional) AniList account for anime sync
- (Optional) Trakt account for watch history import
- (Optional) Overseerr or Seerr instance for requesting
git clone https://github.com/kaishi00/mediocre.git
cd mediocre
# Server
cd server
npm install
cp ../.env.example .env # Edit with your keys & URLs
npm run migrate # Run schema + feedback migrations
node index.js & # Starts on port 3000
# Client (separate terminal)
cd ../client
npm install
npm run dev # Starts dev server with API proxyOpen http://localhost:5173 and enjoy your adequately personalized recommendations.
# Required
TMDB_API_KEY=your_tmdb_key_here
PLEX_URL=http://localhost:32400
PLEX_TOKEN=your_plex_token_here
# Optional - external list imports
ANILIST_TOKEN=your_anilist_token
TRAKT_CLIENT_ID=your_trakt_client_id
TRAKT_CLIENT_SECRET=your_trakt_client_secret
TRAKT_ACCESS_TOKEN=your_trakt_access_token
# Optional - Overseerr/Seerr request proxy
OVERSEERR_URL=https://overseerr.example.com
OVERSEERR_API_KEY=your_overseerr_key
SEERR_URL=https://seerr.example.com
SEERR_API_KEY=your_seerr_key
# Optional
PORT=3000
DB_PATH=./data/seerr.db# Import watched items from Plex
node server/jobs/plexImport.js
# Sync movie/TV metadata from TMDB
node server/jobs/tmdbSync.js
# Pull keywords from TMDB for all catalog items (enables keyword-weighted scoring)
node server/jobs/tmdbKeywordSync.jsββββββββββββ ββββββββββββ ββββββββββββ
β Plex β β AniList β β Trakt β
β Server β β β β β
ββββββ¬ββββββ ββββββ¬ββββββ ββββββ¬ββββββ
β β β
βΌ βΌ βΌ
βββββββββββββββββββββββββββββββββββββββ
β Import / Sync Jobs β
β (plexImport, anilistSync, trakt) β
ββββββββββββββββββββ¬βββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββββββββββββββββ ββββββββββββββββ
β SQLite DB β β TMDB API β
β (catalog, history, ratings, taste) βββββββ (metadata + β
β β β keywords) β
ββββββββββββββββββββ¬ββββββββββββββββββββ ββββββββββββββββ
β
βΌ
βββββββββββββββββββ
β Scoring Engine β
β (3-layer β
β ensemble) β
ββββββββββ¬βββββββββ
β
ββββββββββββββΌβββββββββββββ
βΌ βΌ βΌ
βββββββββββ βββββββββββ βββββββββββββββ
β API β βRequest β β React UI β
β Routes β βProxy β β(6 tabs, β
β β ββOverseerrβ β themes) β
βββββββββββ βββββββββββ βββββββββββββββ
itemsβ TMDB catalog (movie/tv/anime) with popularity scoreswatch_historyβ What you watched, with ratingsuser_ratingsβ Explicit 1β10 ratings (overrides watch history rating)dismissed_itemsβ Items you rejected (with TTL)taste_profilesβ Pre-computed genre + keyword weightsgenres/keywords/item_genres/item_keywordsβ Taxonomy
All configuration is done from the Settings β Recommendations panel in the UI β no config files to edit for tuning.
| Setting | Options | Default |
|---|---|---|
| Catalogue Size | Lean (~500) Β· Standard (~1000) Β· Generous (~2000) | Standard |
| Max Items | Number of top results per scoring pass | 100+ |
Narrow recommendations by themes (genres) and languages. Select multiple to build your taste profile β the engine weights matches against your selected preferences.
Supported themes: Anime, Animation, Comedy, Crime, Documentary, Drama, Adventure, Fantasy, Horror, Kids, Music, Mystery, Romance, Science Fiction, TV Movie, Thriller, Western
Supported languages: English, Japanese, Korean, Chinese, Cantonese, French, Spanish, German, Italian, Portuguese, Czech, Swedish, Danish, Polish, Norwegian, Icelandic, Gaelic, Hindi, Thai
Control how many items appear in each tab via Results Per Tab:
| Tab | Default | Description |
|---|---|---|
| For You | 50 | Personalized ensemble-scored recommendations |
| Movies | 50 | Top scored films |
| TV Shows | 50 | Top scored series |
| Anime Movies | 100 | Top scored anime films (higher default β catalogue is smaller) |
| Anime Series | 100 | Top scored anime series (higher default β catalogue is smaller) |
| Trending | 50 | TMDB popularity-sorted fresh picks |
| Hidden Gems | 50 | Lower-popularity high-scored discoveries |
Type Ratio slider balances movie vs. TV show results across all tabs. Drag to favor one format over the other based on your viewing habits.
The Sync ID tracks your last full profile rebuild. Re-sync anytime after importing new watch history or changing ratings β the engine rebuilds your taste profile from scratch using all available data.
Phase 4 COMPLETE β
- Project scaffolding (23-table schema, Express, migrations)
- TMDB catalog pipeline (200+ items, genre assignments, disk-cached API client)
- Scoring engine (3-layer ensemble with rating weights)
- Feedback system (dismissals, explicit ratings, profile rebuild)
- Keyword sync & keyword-weighted scoring
- Fuzzy title resolver (catalog matching)
- Plex + AniList + Trakt import/sync
- Overseerr/Seerr request integration
- Rich frontend (6 tabs, themes, responsive)
MIT. Do whatever you want with it. It's just mediocre code anyway.
Built with mild frustration at existing recommendation systems that over-promise and under-deliver.
