sdblepas
CinePlete
Python

plex-audit

Last updated Jul 6, 2026
76
Stars
3
Forks
0
Issues
0
Stars/day
Attention Score
54
Language breakdown
Python 54.1%
JavaScript 35.3%
HTML 8.9%
Shell 1.6%
Dockerfile 0.2%
โ–ธ Files click to expand
README

๐ŸŽฌ Cineplete โ€” Plex, Jellyfin & Emby Movie Audit

Build & Publish Docker Docker Pulls Docker Image Version License

Python Self Hosted Multi-Arch FastAPI Playwright

Plex Jellyfin Emby Radarr Overseerr Jellyseerr Trakt Watchtower TMDB Homelab Proxmox GitHub Stars

๐Ÿ‡ซ๐Ÿ‡ท Version franรงaise

Ever wondered which movies you're missing from your favorite franchises, directors, or actors?

Cineplete scans your Plex, Jellyfin or Emby library in seconds and shows exactly what's missing.

โœ” Missing movies from franchises โœ” Missing films from directors you collect โœ” Popular movies from actors already in your library โœ” Classic films missing from your collection โœ” Tailor-made suggestions based on your library

All in a beautiful dashboard with charts and Radarr integration. Supports Plex, Jellyfin and Emby โ€” switchable from the Config tab.

Cineplete Demo

Overview

Cineplete is a self-hosted Docker tool that scans your Plex, Jellyfin or Emby movie library and identifies:

  • Missing movies from franchises
  • Missing films from directors you already collect
  • Popular films from actors already present in your library
  • Classic movies missing from your collection
  • Personalized suggestions based on what your library recommends
  • Metadata issues (missing TMDB GUID or broken matches)
  • Wishlist management
  • Direct Radarr integration
The tool includes a web UI dashboard with charts, a Logs tab for diagnostics, and performs ultra-fast library scans (~2 seconds for Plex, depends on library size for Jellyfin).

Features

Media Server Support โ€” Plex, Jellyfin & Emby

Cineplete supports three media servers. Switch between them from the Config tab โ€” no restart needed.

| | Plex | Jellyfin | Emby | |---|---|---|---| | Scanner | Native XML API | Jellyfin HTTP API | Emby HTTP API | | Speed | ~2s for 1000 movies | Depends on library size | Depends on library size | | Credentials | URL + Token | URL + API Key | URL + API Key | | Library polling | โœ” | โœ” | โœ” | | Test Connection | โœ” | โœ” | โœ” |

Jellyfin setup:

  • In Jellyfin, go to Dashboard โ†’ API Keys and create a new key for Cineplete.
  • In Cineplete Config, set Media Server to Jellyfin, enter the URL, API key, and library name.
  • Use the Test Connection button to verify before saving.
Emby setup:
  • In Emby, go to Dashboard โ†’ API Keys and create a new key for Cineplete.
  • In Cineplete Config, add an Emby library entry, enter the URL (e.g. http://emby:8096), API key, and library name.
  • Use the Test Connection button to verify before saving.

Multiple Libraries Support

New in v3.0: Scan multiple libraries simultaneously โ€” mix Plex and Jellyfin, or connect to multiple servers.

Key features:

  • Concurrent scanning โ€” all enabled libraries scanned in parallel using ThreadPoolExecutor
  • Merged results โ€” duplicates detected automatically across libraries (same TMDB ID)
  • Per-library toggle โ€” enable/disable individual libraries from the Config tab
  • Unified dashboard โ€” all charts and tabs show merged data from all active libraries
  • Progressive optimization โ€” unchanged libraries skip re-analysis (2-3 minutes โ†’ 2 seconds)
Use cases:
  • Scan both Plex (main server) + Jellyfin (4K server)
  • Multiple Plex servers (e.g. home + remote)
  • Separate libraries (Movies + Anime + Foreign)
  • Mix server types in one deployment
Configuration:

Libraries are managed from Config โ†’ Libraries. Each library has:

  • Type โ€” plex, jellyfin or emby
  • Enabled โ€” toggle on/off without deleting credentials
  • Label โ€” friendly name (shown in scan progress)
  • Connection settings โ€” URL, token/API key, library name
Example config.yml:

LIBRARIES:
  - id: "plex-main"
    type: "plex"
    enabled: true
    label: "Plex Main"
    url: "http://192.168.1.10:32400"
    token: "xxxxxxxxxxxx"
    library_name: "Movies"
    page_size: 500
    shortmovielimit: 60

- id: "jellyfin-4k" type: "jellyfin" enabled: true label: "Jellyfin 4K" url: "http://192.168.1.20:8096" api_key: "xxxxxxxxxxxx" library_name: "Movies 4K" page_size: 500 shortmovielimit: 60

- id: "emby-main" type: "emby" enabled: true label: "Emby Main" url: "http://192.168.1.30:8096" api_key: "xxxxxxxxxxxx" library_name: "Movies" page_size: 500 shortmovielimit: 60

Auto-migration from v2.x:

Legacy flat config (single MEDIA_SERVER setting) automatically migrates to the new LIBRARIES list format on first startup. No manual action required โ€” your existing setup continues to work.

Performance:

With 2 libraries (1000 movies each):

  • Sequential: 4 seconds (Plex) + 6 seconds (Jellyfin) = 10 seconds
  • Concurrent: ~6 seconds (parallel scan)

Ultra Fast Plex Scanner

The scanner uses the native Plex XML API instead of slow metadata requests.

Performance example:

  • 1000 movies โ†’ ~2 seconds
  • 3000 movies โ†’ ~4 seconds

Dashboard

The dashboard shows a full visual overview of your library:

Score cards:

  • Franchise Completion %
  • Directors Score %
  • Classics Coverage %
  • Global Cinema Score %
Charts (Chart.js):
  • Franchise Status โ€” doughnut: Complete / Missing 1 / Missing 2+
  • Classics Coverage โ€” doughnut: In library vs missing
  • Metadata Health โ€” doughnut: Valid TMDB / No GUID / No Match
  • Top 10 Actors in library โ€” horizontal bar
  • Directors by missing films โ€” grouped bar (0 / 1โ€“2 / 3โ€“5 / 6โ€“10 / 10+)
  • Library Stats panel
Ignored franchises are excluded from the Franchise Status chart automatically.


Franchises

Detects TMDB collections (sagas) and lists missing films.

Example:

Alien Collection (6/7)
Missing: Alien Romulus

Directors

Detects missing films from directors already in your library.

Example:

Christopher Nolan
Missing: Following, Insomnia

Actors

Finds popular films of actors already in your Plex library.

Filter criteria:

vote_count >= 500

Sorted by popularity, votecount, voteaverage.


Classics

Detects missing films from TMDB Top Rated.

Default criteria:

vote_average >= 8.0
vote_count >= 5000

Suggestions

Personalized movie recommendations based on your own library.

For each film in your Plex library, Cineplete fetches TMDB recommendations and scores each suggested title by how many of your films recommended it. A film recommended by 30 of your movies ranks higher than one recommended by 2.

Each suggestion card shows a โšก N matches badge so you can see at a glance how strongly your library points to it.

API calls are cached permanently โ€” only newly added films incur real HTTP calls on subsequent scans.


Wishlist

Interactive wishlist with UI buttons on every movie card.

Movies can be added from any tab: franchises, directors, actors, classics, suggestions.

Wishlist is stored in:

data/overrides.json

Metadata Diagnostics

No TMDB GUID โ€” Movies without TMDB metadata. Fix inside Plex: Fix Match โ†’ TheMovieDB

TMDB No Match โ€” Films with an invalid TMDB ID that returns no data. The Plex title is shown so you can identify the film immediately. Fix: Refresh metadata or fix match manually in Plex.


Ignore System

Permanently ignore franchises, directors, actors, or specific movies via UI buttons. Ignored items are excluded from all lists and charts.

Movie-level ignore โ€” every movie card now has a ๐Ÿšซ button. Ignored movies appear in a dedicated Ignored tab where they can be restored with one click. Metadata (title, year, poster) is stored so the Ignored tab displays properly even before a rescan.

Stored in:

data/overrides.json

Letterboxd Tab

Import and browse movies from any public Letterboxd URL โ€” watchlists, named lists, diary feeds, or curator profile RSS feeds.

Features:

  • Add multiple URLs โ€” each is fetched and merged into a single scored grid
  • Movies appearing in more than one list get a ร—N badge and are sorted to the top
  • Movies already in your library are automatically filtered out
  • โ†ป refresh button to re-fetch all lists on demand
  • URLs are persisted in overrides.json and survive container restarts
Supported URL formats:

| URL | RSS used | |-----|----------| | letterboxd.com/you/watchlist/ | /watchlist/rss/ | | letterboxd.com/you/list/my-list/ | /list/my-list/rss/ | | letterboxd.com/you/rss/ | used as-is (diary) | | letterboxd.com/you/films/ | falls back to diary /rss/ | | curator profile RSS (e.g. /mscorsese/rss/) | auto-expanded โ€” each linked list's RSS is fetched individually |

All lists must be public. Private Letterboxd accounts or patron-only lists are not accessible.

FlareSolverr Integration

Some Letterboxd list RSS feeds are protected by Cloudflare (403). If you run FlareSolverr in your stack, CinePlete will automatically route blocked requests through it.

Configure the URL in Config โ†’ FlareSolverr (e.g. http://flaresolverr:8191). When a direct fetch returns 403, CinePlete retries via FlareSolverr transparently โ€” no changes needed to your Letterboxd URLs.

flaresolverr:
  image: ghcr.io/flaresolverr/flaresolverr:latest
  container_name: flaresolverr
  ports:
    - "8191:8191"
  restart: unless-stopped

Search, Filter & Sort

All tabs support live filtering:

  • Search by title or group name (director / actor / franchise)
  • Year filter โ€” 2020s / 2010s / 2000s / 1990s / Older
  • Sort โ€” popularity / rating / votes / year / title

Async Scan with Progress

Clicking Rescan launches a background scan immediately without blocking the UI.

A live progress card appears showing:

Step 3/8 โ€” Analyzing collections
[=====>      ] 43%

The progress card disappears automatically when the scan completes.

Only one scan can run at a time. Concurrent scan requests are rejected cleanly.


Logs

A dedicated Logs tab shows the last 200 lines of /data/cineplete.log with color-coded severity levels (ERROR in red, WARNING in amber). Useful for diagnosing scan issues, TMDB API errors, and Plex connectivity problems.

The log file rotates automatically (2 MB ร— 3 files) and never fills your disk.


Version Notifications

CinePlete checks the GitHub Releases API once per hour. When a new version is available, a banner appears in the sidebar below the current version with a direct link to the release notes.


Smart Authentication

CinePlete supports Radarr-style authentication, configurable from Config โ†’ Authentication.

| Mode | Behaviour | |------|-----------| | None | Open access โ€” no login required (default) | | Forms | Username + password required for all access | | Local network free | No auth on local IPs (10.x, 192.168.x, 127.x), login required from internet |

  • Passwords hashed with PBKDF2-SHA256 โ€” never stored in plain text
  • 7-day sliding session cookie โ€” stay logged in across browser sessions
  • "Trust this browser" toggle โ€” persistent vs session cookie
  • Logout button in the sidebar footer
Auth defaults to None โ€” existing deployments are unaffected until you configure it.

API Key Authentication

Generate named API keys from Config โ†’ API Keys to allow headless access from external tools (n8n, Home Assistant, scripts, cron jobs) without a browser session.

Usage โ€” pass the key in any request:

# Header (recommended)
curl -H "X-Api-Key: cpYOURKEY" http://your-server/api/results

Query parameter

curl "http://your-server/api/results?apikey=cpYOURKEY"
Keys are only enforced when Auth Mode is set to Forms or Local network free. With Auth Mode None, the API is open regardless.

Key format: cp_ prefix + 64 random hex characters. Only the SHA-256 hash is stored โ€” the raw key is shown once at creation and cannot be recovered.

All endpoints accessible with an API key:

| Method | Endpoint | Description | |--------|----------|-------------| | GET | /api/results | Full scan results (all tabs) | | POST | /api/scan | Trigger a background scan | | GET | /api/scan/status | Live scan progress | | GET | /api/export | Export to CSV or Letterboxd format | | GET | /api/search | Full-text search across all tabs | | GET | /api/movie/{tmdb_id} | Movie detail (metadata, cast, trailer) | | GET | /api/config | Current configuration | | POST | /api/config | Save configuration | | POST | /api/ignore | Ignore a movie / franchise / director / actor | | POST | /api/unignore | Remove an ignore | | GET | /api/ignored | List all ignored items | | POST | /api/wishlist/add | Add movie to wishlist | | POST | /api/wishlist/remove | Remove from wishlist | | POST | /api/radarr/add | Send movie to Radarr | | POST | /api/overseerr/add | Request movie in Overseerr | | POST | /api/jellyseerr/add | Request movie in Jellyseerr | | GET | /api/streaming/{tmdb_id} | JustWatch streaming availability | | GET | /api/theaters | Now-playing + upcoming films | | GET | /api/quality/upgrades | Movies eligible for 4K upgrade | | GET | /api/trakt/watched | Trakt watched movie IDs | | POST | /api/trakt/watched/refresh | Bust Trakt watched cache | | GET | /api/trakt/status | Trakt connection state | | GET | /api/trakt/debug | Trakt diagnostic info | | GET | /api/letterboxd/urls | Saved Letterboxd URLs | | POST | /api/letterboxd/urls | Add a Letterboxd URL | | POST | /api/letterboxd/urls/remove | Remove a Letterboxd URL | | GET | /api/letterboxd/movies | Scored Letterboxd movie list | | POST | /api/letterboxd/refresh | Refresh Letterboxd cache | | GET | /api/logs | Last N lines of cineplete.log | | GET | /api/cache/info | TMDB cache file info | | POST | /api/cache/backup | Backup TMDB cache | | POST | /api/cache/restore | Restore TMDB cache from backup | | GET | /api/apikeys | List API keys (no hashes exposed) | | POST | /api/apikeys | Generate a new API key | | DELETE | /api/apikeys/{id} | Revoke an API key | | POST | /api/webhook | Trigger scan via webhook | | POST | /api/watchtower/update | Trigger Watchtower auto-update |


Auto-Update via Watchtower

CinePlete can automatically update itself using Watchtower.

Configure it from the Config โ†’ Watchtower section:

  • Toggle Auto-update enabled
  • Set the Watchtower URL (e.g. http://10.0.0.1:8081)
  • Set the API Token (matches WATCHTOWERHTTPAPI_TOKEN on your Watchtower container)
  • Click Update Now to trigger an immediate pull & restart
Watchtower docker-compose example:
watchtower:   image: containrrr/watchtower   environment:     - WATCHTOWERHTTPAPI_UPDATE=true     - WATCHTOWERHTTPAPITOKEN=yourtoken_here     - WATCHTOWERHTTPAPIPERIODICPOLLS=true  # enable scheduled + on-demand     - WATCHTOWERPOLLINTERVAL=86400           # 24h auto-check     - WATCHTOWER_CLEANUP=true   ports:     - "8081:8080"   volumes:     - /var/run/docker.sock:/var/run/docker.sock   restart: unless-stopped

The ?scope=cineplete parameter ensures only CinePlete is updated. Add com.centurylinklabs.watchtower.scope=cineplete to your CinePlete container labels (already included in the docker-compose example above).

Trakt.tv Integration

Connect your Trakt account to overlay your watched history on every movie card and optionally hide already-watched films from all grids.

Setup:

  • Create an application at trakt.tv/oauth/applications to get a Client ID and Client Secret.
  • In CinePlete, go to Config โ†’ Trakt.tv, enter your credentials, and click Connect.
  • A device code appears โ€” visit trakt.tv/activate and enter it.
  • CinePlete polls Trakt automatically and saves your tokens once authorised.
Features:
  • ๐Ÿ”ด Watched badge โ€” a red ribbon appears on every card for films already in your Trakt history
  • ๐Ÿšซ Hide watched โ€” toggle Hide watched movies from all grids to remove watched films from Franchises, Directors, Actors, Classics and Suggestions (Wishlist always shows all films)
  • โŸณ Manual refresh โ€” click Refresh history to force an immediate update (server cache is 1 hour)
  • ๐Ÿ”Œ Disconnect โ€” clears tokens from config.yml immediately; reconnect any time
Config reference:

TRAKT:
  TRAKT_ENABLED: true
  TRAKTCLIENTID: "yourclientid"
  TRAKTCLIENTSECRET: "yourclientsecret"
  TRAKTACCESSTOKEN: ""      # filled automatically after connect
  TRAKTREFRESHTOKEN: ""     # filled automatically after connect
  TRAKT_USERNAME: ""          # filled automatically after connect
  TRAKTHIDEWATCHED: false
Trakt uses Device Code OAuth โ€” no redirect URI required, works behind NAT and reverse proxies. Access tokens are refreshed automatically on expiry.

Radarr Integration

Movies can be added to Radarr with one click from any movie card.

Important: searchForMovie = false

  • โœ” Movie is added to Radarr
  • โœ˜ Download is NOT started automatically

Configuration

Configuration is stored in config/config.yml and editable from the Config tab in the UI.

Basic settings:

| Key | Description | |-----|-------------| | TMDBAPIKEY | TMDB classic API Key (v3) โ€” not the Read Access Token |

โš ๏ธ Use the API Key found under TMDB โ†’ Settings โ†’ API โ†’ API Key (short alphanumeric string starting with letters/numbers). Do not use the Read Access Token (long JWT string starting with eyJ).

Libraries (v3.0+):

Libraries are configured from Config โ†’ Libraries in the UI. Each library entry includes:

| Key | Required | Description | |-----|----------|-------------| | id | Yes | Unique identifier (e.g. plex-0, jellyfin-4k, emby-main) | | type | Yes | plex, jellyfin or emby | | enabled | Yes | true to scan, false to skip | | label | No | Friendly name shown in scan progress | | url | Yes | Server URL (e.g. http://192.168.1.10:32400) | | token | Plex only | Plex authentication token | | api_key | Jellyfin / Emby only | Jellyfin or Emby API key | | library_name | Yes | Name of the movie library | | page_size | No | API page size (default: 500) | | shortmovielimit | No | Skip films shorter than N minutes (default: 60) |

Example multi-library config:

LIBRARIES:
  - id: "plex-main"
    type: "plex"
    enabled: true
    label: "Plex Main"
    url: "http://192.168.1.10:32400"
    token: "xxxxxxxxxxxx"
    library_name: "Movies"

- id: "jellyfin-4k" type: "jellyfin" enabled: false label: "Jellyfin 4K" url: "http://192.168.1.20:8096" api_key: "xxxxxxxxxxxx" library_name: "Movies 4K"

- id: "emby-main" type: "emby" enabled: false label: "Emby Main" url: "http://192.168.1.30:8096" api_key: "xxxxxxxxxxxx" library_name: "Movies"

Legacy config (v2.x): Flat PLEXURL, PLEXTOKEN, JELLYFIN_URL settings still work and auto-migrate to LIBRARIES format on first load.

Advanced settings (accessible via the UI "Advanced settings" section):

| Key | Default | Description | |-----|---------|-------------| | CLASSICS_PAGES | 4 | Number of TMDB Top Rated pages to fetch | | CLASSICSMINVOTES | 5000 | Minimum vote count for classics | | CLASSICSMINRATING | 8.0 | Minimum rating for classics | | CLASSICSMAXRESULTS | 120 | Maximum classic results to return | | ACTORMINVOTES | 500 | Minimum votes for an actor's film to appear | | ACTORMAXRESULTSPERACTOR | 10 | Max missing films shown per actor | | PLEXPAGESIZE | 500 | Plex API page size | | JELLYFINPAGESIZE | 500 | Jellyfin API page size | | SHORTMOVIELIMIT | 60 | Films shorter than this (minutes) are ignored | | SUGGESTIONSMAXRESULTS | 100 | Maximum suggestions to return | | SUGGESTIONSMINSCORE | 2 | Minimum number of your films that must recommend a suggestion |


Installation

Option 1 โ€” Generic LXC / VM / Bare Metal (Debian ยท Ubuntu ยท Raspberry Pi)

One-liner โ€” run inside your container or VM:

curl -fsSL https://raw.githubusercontent.com/sdblepas/CinePlete/main/install/install.sh | sudo bash

What it does: installs Python 3.11+, creates a dedicated cineplete user, downloads the latest release, sets up a Python virtualenv, and registers a systemd service that starts automatically on boot.

Re-running the same command acts as an update.

Post-install management:

journalctl -u cineplete -f          # live logs systemctl restart cineplete          # restart systemctl status cineplete           # status


Option 2 โ€” Proxmox LXC (one command on the Proxmox host)

Run this on your Proxmox host as root โ€” it creates a fresh unprivileged Debian 12 LXC and installs CinePlete inside it automatically:

bash -c "$(curl -fsSL https://raw.githubusercontent.com/sdblepas/CinePlete/main/install/proxmox-lxc.sh)"

With custom options:

CTID=200 CTIP=192.168.1.50/24 CTGW=192.168.1.1 CTRAM=1024 \   bash -c "$(curl -fsSL https://raw.githubusercontent.com/sdblepas/CinePlete/main/install/proxmox-lxc.sh)"

| Variable | Default | Description | |----------|---------|-------------| | CT_ID | next available | LXC container ID | | CT_IP | dhcp | Static IP in CIDR notation (e.g. 192.168.1.50/24) | | CTGW | (none)_ | Gateway for static IP | | CT_CORES | 2 | CPU cores | | CT_RAM | 512 | RAM in MB | | CT_DISK | 4 | Disk in GB | | CT_BRIDGE | vmbr0 | Network bridge | | PORT | 7474 | App listen port |

Post-install management from the Proxmox host:

pct exec <CT_ID> -- journalctl -u cineplete -f     # live logs pct exec <CT_ID> -- bash                            # open shell 

Update:

pct exec <CT_ID> -- bash -c "curl -fsSL https://raw.githubusercontent.com/sdblepas/CinePlete/main/install/install.sh | bash"


Option 3 โ€” Docker Compose (recommended)

version: "3.9"
services:
  cineplete:
    image: sdblepas/cineplete:latest
    container_name: cineplete
    ports:
      - "8787:8787"
    volumes:
      - /path/to/config:/config
      - /path/to/data:/data
    labels:
      net.unraid.docker.webui: "http://[IP]:[PORT:8787]"
      net.unraid.docker.icon: "https://raw.githubusercontent.com/sdblepas/CinePlete/main/assets/icon.png"
      org.opencontainers.image.url: "http://localhost:8787"
      com.centurylinklabs.watchtower.scope: "cineplete"
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8787')"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 20s
    restart: unless-stopped

Port conflict? Add APP_PORT to change the internal port:

environment:
  - APP_PORT=8788
ports:
  - "8788:8788"

Start:

docker compose up -d

Open UI:

http://YOURNASIP:8787

Project Structure

CinePlete/
โ”œโ”€โ”€ .github/
โ”‚   โ””โ”€โ”€ workflows/
โ”‚       โ””โ”€โ”€ docker.yml        # CI/CD pipeline (scan โ†’ test โ†’ version โ†’ build)
โ”œโ”€โ”€ app/
โ”‚   โ”œโ”€โ”€ web.py                # FastAPI backend + all API endpoints
โ”‚   โ”œโ”€โ”€ scanner.py            # 8-step scan engine (threaded)
โ”‚   โ”œโ”€โ”€ plex_xml.py           # Plex XML API scanner
โ”‚   โ”œโ”€โ”€ jellyfin_api.py       # Jellyfin HTTP API scanner
โ”‚   โ”œโ”€โ”€ emby_api.py           # Emby HTTP API scanner
โ”‚   โ”œโ”€โ”€ scheduler.py          # Library auto-poll scheduler (Plex, Jellyfin & Emby)
โ”‚   โ”œโ”€โ”€ tmdb.py               # TMDB API client (cached, key-safe, error logging)
โ”‚   โ”œโ”€โ”€ overrides.py          # Ignore/wishlist/recfetchedids helpers
โ”‚   โ”œโ”€โ”€ config.py             # Config loader/saver with deep-merge
โ”‚   โ””โ”€โ”€ logger.py             # Shared rotating logger (console + file)
โ”œโ”€โ”€ static/
โ”‚   โ”œโ”€โ”€ index.html            # Single-page app shell + all CSS
โ”‚   โ””โ”€โ”€ app.js                # All UI logic: routing, rendering, API calls
โ”œโ”€โ”€ assets/
โ”‚   โ””โ”€โ”€ icon.png              # App icon (used by Unraid WebUI label)
โ”œโ”€โ”€ config/
โ”‚   โ””โ”€โ”€ config.yml            # Default config template
โ”œโ”€โ”€ tests/
โ”‚   โ”œโ”€โ”€ test_config.py
โ”‚   โ”œโ”€โ”€ testjellyfinapi.py  # 16 unit tests for Jellyfin scanner
โ”‚   โ”œโ”€โ”€ test_overrides.py
โ”‚   โ”œโ”€โ”€ test_scheduler.py
โ”‚   โ””โ”€โ”€ test_scoring.py
โ”œโ”€โ”€ docker-compose.yml
โ”œโ”€โ”€ Dockerfile
โ””โ”€โ”€ README.md

Data Files

All persistent data lives in the mounted /data volume and survives container updates:

| File | Description | |------|-------------| | results.json | Full scan output โ€” regenerated on each scan | | tmdb_cache.json | TMDB API response cache โ€” persists between scans | | overrides.json | Ignored items, wishlist, recfetchedids | | cineplete.log | Rotating log file (2 MB ร— 3 files) |


API Endpoints

All endpoints below are accessible with an API key (X-Api-Key header or ?apikey= query param) when auth is enabled. See API Key Authentication for details.

| Method | Endpoint | Description | |--------|----------|-------------| | GET | /api/version | Current app version + update check | | GET | /api/results | Full scan results (all tabs) | | POST | /api/scan | Start a background scan | | GET | /api/scan/status | Live scan progress (8 steps) | | GET | /api/export | Export to CSV or Letterboxd format | | GET | /api/search | Full-text search across all tabs | | GET | /api/movie/{tmdb_id} | Movie detail (metadata, cast, trailer) | | GET | /api/config | Current configuration | | POST | /api/config | Save configuration | | GET | /api/config/status | Returns {configured: bool} | | POST | /api/library/test | Test Plex / Jellyfin / Emby connection | | POST | /api/ignore | Ignore a movie / franchise / director / actor | | POST | /api/unignore | Remove an ignore | | GET | /api/ignored | List all ignored items with metadata | | POST | /api/wishlist/add | Add a movie to wishlist | | POST | /api/wishlist/remove | Remove from wishlist | | POST | /api/radarr/add | Send a movie to Radarr | | GET | /api/radarr/profiles | Fetch Radarr quality profiles | | GET | /api/radarr/rootfolders | Fetch Radarr root folders | | GET | /api/radarr/library | TMDB IDs of all Radarr movies | | GET | /api/radarr/status | Per-movie Radarr status | | POST | /api/overseerr/add | Request a movie in Overseerr | | POST | /api/jellyseerr/add | Request a movie in Jellyseerr | | GET | /api/streaming/{tmdb_id} | JustWatch streaming availability | | GET | /api/theaters | Now-playing + upcoming films | | GET | /api/quality/upgrades | Movies eligible for 4K upgrade | | POST | /api/quality/refresh | Bust quality upgrade cache | | GET | /api/trakt/watched | Trakt watched movie IDs (cached 1 h) | | POST | /api/trakt/watched/refresh | Bust Trakt watched cache | | GET | /api/trakt/status | Trakt connection state | | GET | /api/trakt/debug | Trakt diagnostic info (no tokens exposed) | | POST | /api/trakt/device/code | Start Trakt device-code OAuth flow | | POST | /api/trakt/device/poll | Poll Trakt OAuth approval | | POST | /api/trakt/disconnect | Revoke and clear Trakt tokens | | GET | /api/letterboxd/urls | Saved Letterboxd URLs | | POST | /api/letterboxd/urls | Add a Letterboxd URL | | POST | /api/letterboxd/urls/remove | Remove a Letterboxd URL | | GET | /api/letterboxd/movies | Scored Letterboxd movie list | | POST | /api/letterboxd/refresh | Refresh Letterboxd cache | | GET | /api/apikeys | List API keys (no hashes exposed) | | POST | /api/apikeys | Generate a new API key | | DELETE | /api/apikeys/{id} | Revoke an API key | | GET | /api/cache/info | TMDB cache file age and size | | POST | /api/cache/backup | Back up TMDB cache | | POST | /api/cache/restore | Restore TMDB cache from backup | | GET | /api/logs | Last N lines of cineplete.log | | GET | /api/auth/status | Current auth mode and login state | | POST | /api/auth/login | Authenticate with username + password | | POST | /api/auth/logout | Clear the session cookie | | POST | /api/webhook | Trigger scan via authenticated webhook | | POST | /api/watchtower/update | Trigger Watchtower auto-update |


Technologies

  • Python 3.11
  • FastAPI + Uvicorn
  • Docker (multi-arch: amd64 + arm64)
  • TMDB API v3
  • Plex XML API
  • Jellyfin HTTP API
  • Emby HTTP API
  • Chart.js
  • Tailwind CSS (CDN)

Architecture

Plex Server          Jellyfin Server          Emby Server
     โ”‚                      โ”‚                      โ”‚
     โ”‚ XML API               โ”‚ HTTP REST API         โ”‚ HTTP REST API
     โ”‚ (~2s/1000 movies)     โ”‚ (paginated)           โ”‚ (/emby/ prefix)
     โ–ผ                      โ–ผ                      โ–ผ
 plexxml.py         jellyfinapi.py          emby_api.py
  (TMDB IDs,          (TMDB IDs,               (TMDB IDs,
   directors,          directors,               directors,
   actors,             actors,                  actors,
   duplicates)         top 5/film)              top 5/film)
       \                   |                   /
        โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ scan_movies() โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                           โ”‚
                           โ–ผ
            8-Step Scan Engine โ€” scanner.py (background thread)
                           โ”‚
     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
     โ–ผ                     โ–ผ                       โ–ผ
 Franchises            Directors               Actors
 (TMDB collections)    (personcredits)        (votecount โ‰ฅ 500)
     โ”‚                     โ”‚                       โ”‚
     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ TMDB API client โ€” tmdb.py โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                  (thread-safe, disk-cached,
                   key-normalized, flush/50 calls)
                           โ”‚
     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
     โ–ผ                     โ–ผ                       โ–ผ
  Classics             Suggestions             Scores
 (Top Rated TMDB)    (recommendations        (franchise / directors /
                      scored by library       classics / global)
                      match count)
                           โ”‚
                           โ–ผ
                     results.json โ—„โ”€โ”€โ”€โ”€ overrides.json
                     (/data volume)     (wishlist, ignores,
                           โ”‚            letterboxd_urls,
                           โ–ผ            recfetchedids)
                   FastAPI โ€” web.py
                   (42 API endpoints)
         โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
         โ–ผ                 โ–ผ                  โ–ผ
    Radarr / 4K      Overseerr /         Letterboxd
    (add, status,    Jellyseerr          (RSS โ†’ TMDB,
     grab poller)    (request)           multi-URL merge,
         โ”‚                               FlareSolverr)
         โ–ผ
    Telegram
    (scan summary +
     grab notifications)
                           โ”‚
                           โ–ผ
         Single-page app โ€” static/js/
    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
    โ”‚ app.js โ”‚scan.js โ”‚ api.js โ”‚render.jsโ”‚config.jsโ”‚filters โ”‚modal.jsโ”‚
    โ”‚routing โ”‚polling โ”‚ fetch  โ”‚tabs /   โ”‚config   โ”‚search/ โ”‚detail  โ”‚
    โ”‚state   โ”‚progressโ”‚toast   โ”‚cards /  โ”‚form /   โ”‚filter/ โ”‚modal / โ”‚
    โ”‚nav     โ”‚badges  โ”‚utils   โ”‚charts   โ”‚cache    โ”‚sort    โ”‚trailer โ”‚
    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ”— More in this category

ยฉ 2026 GitRepoTrend ยท sdblepas/CinePlete ยท Updated daily from GitHub