172 lines
6.0 KiB
Markdown
172 lines
6.0 KiB
Markdown
# 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.
|