Files
horror/README.md
2026-08-12 19:30:41 +02:00

172 lines
6.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 2448 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.