Filmabend
Privater Katalog-Browser für den wöchentlichen Filmabend. Vier Reiter:
- Netflix — alles im Netflix-DE-Abo ab 50 Stimmen, aktuell 2567 Filme.
- Kanon — 194 Einträge aus Horror-Bestenlisten, davon 28 Filmreihen. Zusammen 319 Filme, unabhängig davon, wo sie laufen.
- Suche — freie TMDB-Suche für alles, was in keinem der beiden steht.
- Rad — Glücksrad über die eingereichten Vorschläge.
Daten kommen von TMDB (Provider-Info stammt dort aus der JustWatch-Partnerschaft). Die deutschen Teaser schreibt MiniMax M3 beim Sync und landen in der SQLite-Datei.
Einrichten
cp .env.example .env # TMDB_TOKEN und MINIMAX_KEY eintragen
make install
Sync
Holt den Katalog und erzeugt fehlende Teaser. Der erste Lauf dauert einige Minuten, weil für jeden Film ein Teaser generiert wird.
make sync
Teaser werden nur einmal erzeugt. Spätere Syncs schreiben nur für neue Filme. Im Betrieb läuft der Sync täglich um 4:30 von selbst.
Entwickeln
make dev # Backend :8000, Frontend :5173
make stop
Der Vite-Dev-Server leitet /api an den Backend-Port weiter.
Produktiv
Auf dem Server im Projektverzeichnis. Läuft hinter Traefik im externen
Docker-Netz web, wie die anderen Projekte.
make prod # docker compose up -d --build
make logs
make down
Die SQLite-Datei liegt im Bind-Mount ./data, überlebt also jeden Rebuild.
Ohne Docker geht es auch:
make build # Vue nach backend/static
cd backend && .venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000
Wie es läuft
APScheduler (täglich 4:30)
│
▼
TMDB /discover/movie ──► pro Film /movie/{id} (Bilder, Videos, Übersetzungen)
│ │
│ ▼
│ MiniMax M3 schreibt den Teaser
▼ │
SQLite (backend/horror.db) ◄────────────────┘
│
▼
FastAPI /api/movies ──► Vue-Grid, Detail-Overlay
API
| Endpoint | Zweck |
|---|---|
GET /api/movies |
ganzer Katalog, das Frontend filtert clientseitig |
GET /api/movies/{id} |
einzelner Film |
GET /api/status |
Anzahl Filme, letzter Sync, läuft gerade einer |
GET /api/search?q= |
freie TMDB-Suche, nicht auf den Katalog beschränkt |
GET /api/search/{id} |
volle Daten, erst aus dem Katalog, sonst von TMDB |
GET /api/health |
für den Docker-Healthcheck |
POST /api/sync |
Sync von Hand anstoßen |
Filmreihen
Gehört ein Film zu einer Reihe, zeigt die Detailansicht die anderen Teile —
aber nur die, die selbst auf Netflix liegen. Grundlage ist TMDBs
belongs_to_collection. Ein Klick springt direkt zum nächsten Teil.
Im Raster stehen Reihen als eine Karte, die durch ihre Teile blättert. Aus 2567 Netflix-Kacheln werden so 2369 — Herr der Ringe belegt eine statt drei. Die Karte startet beim bestbewerteten Teil, danach richtet sich auch ihr Platz im Raster.
Vorschläge und Rad
Im Reiter Rad landen die Filmvorschläge. Jede Sitzung darf einen Film vorschlagen — erkannt über ein Cookie, ohne Anmeldung. Ein zweiter Vorschlag ersetzt den ersten, statt abgelehnt zu werden.
Vorgeschlagen wird aus der Detailansicht heraus, mit optionalem Namen. Das klappt auch mit Filmen aus dem Suche-Reiter, die gar nicht im Katalog stehen: Titel, Jahr und Poster werden beim Vorschlagen mitgespeichert.
Das Rad verteilt die Vorschläge auf gleich große Segmente und dreht fünf bis sieben Runden. Der Zeiger steht oben; das Ziel wird ausgelost und die Drehung so berechnet, dass die Segmentmitte dort landet.
Gedreht wird für alle gleichzeitig. Die Ziehung passiert im Server, nicht im Browser — sonst sähe jede Seite ein anderes Ergebnis. Wer auf „Drehen" klickt, stößt nur an; das Ergebnis samt Zielwinkel geht per Server-Sent Events an alle offenen Seiten. Neue Vorschläge kommen auf demselben Weg an.
Wer die Seite mitten im Dreh lädt, fragt GET /api/spin und steigt in die
Restanimation ein. Ist der Dreh schon durch, springt das Rad ohne Animation
auf die Endstellung — sonst zeigte der Zeiger woanders hin als die
Gewinnerkarte.
Die Abonnenten liegen im Speicher des Prozesses. Das trägt genau so weit wie ein einzelner uvicorn-Worker; bei mehreren bekäme jeder nur seine eigenen.
| Endpoint | Zweck |
|---|---|
GET /api/suggestions |
alle Vorschläge, plus die eigene ID |
POST /api/suggestions |
vorschlagen oder ersetzen |
DELETE /api/suggestions |
eigenen Vorschlag zurückziehen |
GET /api/events |
Live-Kanal (SSE) für Dreh und Vorschläge |
POST /api/spin |
Dreh für alle auslösen |
GET /api/spin |
laufenden oder letzten Dreh abfragen |
Die Filterleiste
Rechts am Fensterrand, fest stehend. Von oben nach unten:
- Suche und Sortierung
- Vier Bereichsregler: Bewertungen, Dauer, Jahr, Bewertung. Die Skalen kommen aus dem offenen Reiter und wechseln beim Umschalten mit. Bei den Stimmen ist die Skala gestaucht — linear säßen fast alle Filme im linken Prozent.
- Genres zum Zeigen und zum Ausblenden, je als Badge-Gruppe mit "alle"
Eingeschlossene Genres wirken als ODER. Ein Ausschluss sticht immer: Ein Actionfilm mit Komödie fliegt raus, wenn Komödie ausgeblendet ist.
Ein früherer Versuch mit acht Subgenres (Geister, Zombies, Slasher …) ist wieder raus. TMDBs Schlagwörter reichten dafür nicht: 77 Filme hatten gar keine, der Rest nur allgemeine wie "supernatural horror".
Der Kanon-Reiter
Die Liste steht in app/canon_list.py. Ein Eintrag ist ein Film oder eine
Reihe. Reihen bekommen eine breitere Karte, die durch ihre Teile blättert —
immer ein Teil vollständig sichtbar.
app/canon.py löst die deutschen Verleihtitel gegen TMDB auf. Das klappt bei
309 von 320 Titeln allein über die Suche. Der Rest steht in OVERRIDES mit
fester TMDB-ID, weil die Suche danebengriff:
- Ohne Jahresangabe liefert TMDB den jüngsten Titel zuerst. "Final Destination" traf deshalb "Bloodlines", "A Quiet Place" den zweiten Teil.
- "Das Böse" ist auf Deutsch sowohl Phantasm als auch Amityville Horror.
- "Us" traf "Forgive Us All" von 2025.
Die Vorlage führte "Black Christmas" und "Jessy – Die Treppe in den Tod" getrennt. Das ist derselbe Film, hier nur einmal.
Innerhalb einer Reihe wird nach Erscheinungsjahr sortiert, nicht nach der Reihenfolge der Vorlage. Die gruppiert das Conjuring-Universum nach Sub-Reihen — erst alle Conjuring, dann Nun, dann Annabelle.
Woher der Netflix-Link kommt
Die TMDB-API nennt nur den Anbieternamen, nicht die Titel-ID. Die steht
ausschließlich im HTML der Watch-Seite, URL-kodiert im JustWatch-Link.
app/netflix.py liest sie dort aus und merkt sie sich in der Spalte
netflix_id, damit spätere Syncs die Seite nicht erneut laden.
Der Abruf läuft seriell mit 1,5 Sekunden Pause. Bei dichteren Abrufen antwortet TMDB mit Status 200, aber ohne Anbieterblock — die ID fehlt dann stillschweigend. Filme ohne ID versucht der nächste Sync erneut.
Hat ein Film mehrere IDs, gewinnt die erste. Godzilla Minus One liegt bei Netflix in Farbe und in Schwarzweiß.
Warum der Katalog bei 50 Stimmen abschneidet
JustWatch ordnet unbekannte Titel gelegentlich der Netflix-ID eines gleichnamigen bekannten Films zu. TMDB übernimmt das ungeprüft. Der Film steht dann im Katalog, obwohl Netflix ihn nicht hat.
Nachgewiesen an fünf Fällen, alle nach demselben Muster:
| Eintrag | Stimmen | bekam die ID von |
|---|---|---|
| CREEP (2014, 8 Min) | 2 | Creep (2014, 1678 Stimmen) |
| Muerte, muerte, muerte | 2 | Bodies Bodies Bodies |
| LIENZO | 1 | Die Leinwand / Canvas |
| NOISE! | 1 | Noise |
| The Roommate (2022) | 1 | The Roommate (2011) |
Nachweis über TMDBs Watch-Seiten: Dort steht die Netflix-ID im Weiterleitungslink. Zwei Filme mit derselben ID können nicht beide stimmen. Oberhalb von zwei Stimmen trat kein einziger Fall auf. Die Grenze liegt inzwischen bei 50 Stimmen, weil der Katalog alle Genres umfasst — das hält die Fehlzuordnungen ohnehin draußen.
Grenzen
- Verfügbarkeit hinkt Netflix 24–48 h hinterher. So oft liefert JustWatch an TMDB.
- Es gibt keine Info, ob ein Film deutsche Synchronisation hat. TMDB kennt nur die Originalsprache, nicht die Tonspuren bei Netflix.
- Der Netflix-Knopf führt bei 730 von 2567 Filmen direkt auf die Titelseite. Der Rest landet auf TMDBs Watch-Seite, von dort ist es ein Klick weiter. Pro Sync kommen 250 dazu, meistgesehene zuerst — mehr auf einmal drosselt TMDB.
- Die Szenenbilder sind TMDB-Backdrops. Der Sync wirft Duplikate raus, indem
er die Bilder selbst vergleicht (dHash plus Farbhistogramm, siehe
app/imagehash.py). Motive mit Titeltext fliegen ganz raus. Werbemotive ohne Text bleiben drin — die lassen sich nicht von Szenenfotos trennen. - Neun Filme haben gar keine Bilder. Dann zeigt die Detailansicht weniger.
- Der Teaser-Ton richtet sich nach dem Genre. Horror, Thriller, Mystery und Krimi bleiben reißerisch, alles andere bekommt die Anweisung, den Ton des Films zu treffen. Vorher wurde aus einer Bollywood-Romanze ein Dorf mit verschlingenden Almwiesen.
Attribution
This product uses the TMDB API but is not endorsed or certified by TMDB. Verfügbarkeitsdaten stammen von JustWatch.