# Horrorfilme Privater Katalog-Browser mit zwei Reitern: - **Netflix** — alle Horrorfilme im Netflix-DE-Abo, aktuell 325. - **Kanon** — 194 Einträge aus anerkannten Bestenlisten, davon 28 Filmreihen. Zusammen 320 Filme, unabhängig davon, wo sie laufen. 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 ```bash 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. ```bash 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 ```bash 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. ```bash 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: ```bash 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/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. Aktuell haben 86 Filme eine Reihe, 24 Reihen sind mit mehr als einem Teil vertreten. Die größte ist Resident Evil mit fünf Filmen. ## 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 drei 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, deshalb die Grenze bei drei. Das kostet 11 Einträge von 336. ## 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 310 von 325 Filmen direkt auf die Titelseite. Die restlichen 15 landen auf TMDBs Watch-Seite, weil dort kein Netflix-Link hinterlegt ist. Der nächste Sync versucht sie erneut. - 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. - 15 Filme haben gar keine Bilder, 46 keinen Trailer. Dann zeigt die Detailansicht entsprechend weniger. ## Attribution This product uses the TMDB API but is not endorsed or certified by TMDB. Verfügbarkeitsdaten stammen von JustWatch.