Neue Spalten cover_image (BYTEA) und cover_mime_type (TEXT) in n8n.movie_files, damit Cover-Bilder direkt in der DB gespeichert werden können statt nur als externe URL. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Filebot Media Browser
Web-Oberfläche zur Anzeige und Verwaltung von Medien-Metadaten aus PostgreSQL (Filebot / n8n). Fokus: Original- und neuer Dateiname zum Prüfen von Renames, plus Cover, Filtern und Gesehen-Status.
Inhaltsverzeichnis
- Überblick
- Features
- Architektur
- Voraussetzungen
- Installation auf dem Server
- Konfiguration
- Datenbank
- Bedienung
- API-Referenz
- Projektstruktur
- Entwicklung & Updates
- Fehlerbehebung
- Hinweise für n8n
Überblick
Die Anwendung läuft als zwei Docker-Container auf einem Linux-Server:
- web – Nginx liefert die Vue-App aus und leitet
/apian das Backend weiter - api – FastAPI liest Daten aus PostgreSQL
Produktiv-Pfad auf dem Server: /opt/nginx_filebot
Auf dem Entwicklungs-PC (z. B. Mac) ist kein Node.js oder Python für den Betrieb nötig. Code anpassen, auf den Server kopieren, dort
docker compose up -d --buildausführen.
Für KI-Assistenten / Projekt-Kontext: siehe PROMPT.md.
Features
Medien-Übersicht
- Cover aus
cover_url(z. B. TVMaze-Poster) in der ersten Spalte / auf Karten - Original- und Neu-Dateiname immer sichtbar
- Technische Spalten: Rating, Format, Auflösung, Codecs, Bitraten, Container, Erstellungsdatum
- Neueste Einträge zuerst (Sortierung nach
created_at)
Filter
- Volltextsuche in beiden Dateinamen
- Button-Filter für Videoformat, Codec und Container (nebeneinander auf Desktop, gestapelt auf Mobil)
- Zeitraum „Von / Bis“ mit Kalender und Uhrzeit (HH:MM)
- Schnellfilter in der Tabelle (clientseitig in geladenen Daten)
Gesehen-Status
- Ungesehene Zeilen sind hervorgehoben (blauer Akzent)
- Tippen auf eine Zeile markiert sie als gesehen oder ungesehen
- Filter „Nur ungesehen“ und Aktion „Alle als gesehen“ (für aktuelle Filterauswahl)
- Zähler „X neu“ im Header und in den Filtern
Weitere Aktionen
- Löschen einzelner Einträge (Mülleimer, mit Bestätigung)
- Responsives Layout: Tabelle (Desktop) / Karten (Mobil, ≤900px Breite)
Architektur
Browser
│
▼
┌─────────────────┐ /api/* ┌─────────────────┐
│ nginx (web) │ ───────────────►│ FastAPI (api) │
│ Port 8080 │ │ Port 8000 │
│ Vue static │ └────────┬────────┘
└─────────────────┘ │
▼
┌─────────────────┐
│ PostgreSQL │
│ n8n.movie_files│
└─────────────────┘
| Container | Image-Build | Aufgabe |
|---|---|---|
web |
nginx/Dockerfile (Node → Nginx) |
Statische Dateien, Reverse Proxy |
api |
api/Dockerfile |
REST-API, DB-Zugriff |
Compose-Datei nutzt relative Pfade – immer aus /opt/nginx_filebot starten.
Voraussetzungen
- Linux-Server mit Docker und Docker Compose v2
- Erreichbare PostgreSQL-Instanz (im LAN, z. B.
192.168.30.186:5432) - Tabelle
n8n.movie_filesin Datenbankn8n_filebot(anpassbar in Config) - Optional: Spalten
seen_atundcover_url(siehe Datenbank)
Installation auf dem Server
Ausführliche Anleitung inkl. SMB-Gruppe und Umzug: docs/DEPLOY-NEUER-SERVER.md.
1. Projekt ablegen
# Ein User (vogto): Rechte auf ganz /opt, nicht nur ein Unterordner
sudo groupadd -f opt-docker
sudo usermod -aG opt-docker,docker vogto
sudo chown vogto:opt-docker /opt && sudo chmod 2775 /opt
sudo mkdir -p /opt/nginx_filebot
# Variante A: Git
git clone <repository-url> /opt/nginx_filebot
# Variante B: rsync vom Entwicklungsrechner
# rsync -avz --exclude node_modules --exclude config/config.yaml \
# ./ user@server:/opt/nginx_filebot/
2. Datenbank vorbereiten
Siehe Abschnitt Datenbank – Migrationen ausführen.
3. Konfiguration anlegen
cd /opt/nginx_filebot
cp config/config.example.yaml config/config.yaml
nano config/config.yaml
4. Starten
cd /opt/nginx_filebot
docker compose up -d --build
5. Prüfen
docker compose ps
curl -s http://localhost:8080/api/health
# Erwartung: {"status":"ok"}
Im Browser: http://<Server-IP>:8080
Konfiguration
Datei: /opt/nginx_filebot/config/config.yaml (nicht versionieren, in .gitignore).
Beispiel (produktive Werte aus dem Projekt):
database:
host: "192.168.30.186"
port: 5432
name: "n8n_filebot" # PostgreSQL-Datenbankname
user: "n8n_filebot"
password: "geheim"
table: "movie_files" # Tabellenname (ohne Schema)
db_schema: "n8n" # PostgreSQL-Schema
ssl: false
| Parameter | Bedeutung |
|---|---|
name |
Datenbank (Catalog), nicht Tabellenname |
table |
Tabellenname innerhalb des Schemas |
db_schema |
Schema (bei euch typisch n8n) |
schema |
Alias für db_schema in YAML (optional) |
Die API verbindet sich mit: "n8n"."movie_files" (in Anführungszeichen, case-sensitiv).
Datenbank
Tabelle und Schema
| Einstellung | Typischer Wert |
|---|---|
| Datenbank | n8n_filebot |
| Schema | n8n |
| Tabelle | movie_files |
Tabellen prüfen:
docker compose exec api python /app/scripts/list-tables.py
Migration: seen_at
psql -h 192.168.30.186 -U n8n_filebot -d n8n_filebot \
-f /opt/nginx_filebot/sql/001_add_seen_at.sql
Oder manuell:
ALTER TABLE n8n.movie_files
ADD COLUMN IF NOT EXISTS seen_at TIMESTAMPTZ NULL;
CREATE INDEX IF NOT EXISTS idx_movie_files_seen_at
ON n8n.movie_files (seen_at);
seen_at IS NULL→ ungesehen (hervorgehoben in der UI)- gesetzt → gesehen
Migration: cover_url
psql -h 192.168.30.186 -U n8n_filebot -d n8n_filebot \
-f /opt/nginx_filebot/sql/002_add_cover_url.sql
ALTER TABLE n8n.movie_files
ADD COLUMN IF NOT EXISTS cover_url TEXT NULL;
Beispiel-URL: https://static.tvmaze.com/uploads/images/medium_portrait/200/502332.jpg
(Doppelte Anführungszeichen in der DB werden in der UI bereinigt.)
Weitere Spalten
Die API liest alle in api/main.py → COLUMNS definierten Felder (entsprechen dem n8n/Filebot-Export). Fehlende Spalten führen zu API-Fehlern – dann Spalte in PostgreSQL ergänzen oder COLUMNS anpassen.
Bedienung
Desktop
- Seite öffnen → Daten werden geladen (Status „DB verbunden“).
- Filter oben nutzen; Aktualisieren lädt neu, Zurücksetzen löscht Filter.
- Live-Aktualisierung: Alle 5 Sekunden werden die Daten still neu geladen (ohne Seiten-Reload). Im Header: „Live · HH:MM:SS“. Im Hintergrund-Tab pausiert das Polling.
- Cover: Bilder werden über
/api/covergeladen (Proxy), falls externe Poster (z. B. TVMaze) blockiert werden. - Zeile antippen/klicken → Gesehen-Status umschalten.
- Mülleimer → Eintrag löschen (mit Bestätigung).
- Spaltenköpfe klicken zum Sortieren; Schnellfilter unter der Filterleiste.
Mobil
- Gleiche Logik in Kartenform mit Cover links.
- Filter untereinander.
Gesehen-Status
| Aktion | Effekt |
|---|---|
| Zeile antippen (ungesehen) | seen_at = jetzt |
| Zeile antippen (gesehen) | seen_at = NULL |
| „Nur ungesehen“ | Zeigt nur Einträge ohne seen_at |
| „Alle als gesehen“ | Setzt seen_at für alle aktuell gefilterten Einträge |
API-Referenz
Basis-URL hinter Nginx: http://<host>:8080/api
GET /health
{ "status": "ok" }
GET /status
Diagnose: Config-Pfad, qualifizierte Tabelle, Zeilenanzahl, bei Fehler Tabellenlisten.
GET /records
Query-Parameter:
| Parameter | Beschreibung |
|---|---|
search |
ILIKE in file_name_original und file_name_new |
format |
standard_video_format |
codec |
video_codec_library |
container |
container_format |
created_from |
ISO-Datum/Zeit, created_at >= |
created_to |
ISO-Datum/Zeit, created_at <= |
unseen_only |
true → nur seen_at IS NULL |
limit, offset |
Pagination (Standard limit=5000) |
Antwort enthält u. a. records, total, unseen_total.
GET /records/meta
Gleiche Query-Parameter wie GET /records (ohne Pagination). Leichtgewichtige Antwort für Auto-Refresh:
{
"total": 120,
"unseen_total": 3,
"max_id": 456,
"latest_created_at": "2026-06-04T12:00:00+00:00",
"revision": "a1b2c3…"
}
GET /cover
Query: url (HTTP(S)-Poster-URL). Liefert das Bild über die API (umgeht Hotlink-/Referrer-Sperren).
GET /filters
Distinct-Werte für Format-, Codec- und Container-Buttons.
PATCH /records/{id}/seen
Body:
{ "seen": true }
oder { "seen": false } (setzt seen_at auf NULL).
POST /records/mark-all-seen
Markiert alle Einträge, die zu den gleichen Query-Filtern wie GET /records passen (ohne unseen_only).
DELETE /records/{id}
Löscht einen Datensatz anhand der id.
Projektstruktur
.
├── docker-compose.yml
├── PROMPT.md # KI-/Projekt-Kontext
├── README.md
├── config/
│ └── config.example.yaml
├── api/
│ ├── main.py # REST-Endpunkte, COLUMNS
│ ├── config.py # YAML-Config, db_schema
│ ├── Dockerfile
│ ├── requirements.txt
│ └── scripts/
│ └── list-tables.py # Diagnose im Container
├── frontend/
│ ├── package.json
│ ├── vite.config.ts
│ └── src/
│ ├── App.vue
│ ├── api.ts
│ ├── styles.css # Theme (helles Dark-UI)
│ ├── components/
│ │ ├── DataTable.vue
│ │ ├── MobileCards.vue
│ │ ├── FilterBar.vue
│ │ ├── CoverImage.vue
│ │ ├── NamePair.vue
│ │ ├── DateTimeRangeFilter.vue
│ │ └── DeleteButton.vue
│ └── utils/
│ ├── datetime.ts
│ └── seen.ts
├── nginx/
│ ├── Dockerfile
│ └── nginx.conf
└── sql/
├── 001_add_seen_at.sql
└── 002_add_cover_url.sql
Entwicklung & Updates
Code aktualisieren
cd /opt/nginx_filebot
git pull # falls Git
docker compose up -d --build
Nur Frontend:
docker compose up -d --build web
Nur API:
docker compose up -d --build api
Logs
docker compose logs -f api
docker compose logs -f web
Optional: lokale Entwicklung (nicht für Produktion)
Nur wenn bewusst gewünscht – normaler Workflow ist Server-only.
# API
cd api && pip install -r requirements.txt
CONFIG_PATH=../config/config.yaml uvicorn main:app --reload --port 8000
# Frontend (proxied in vite.config.ts nach :8000)
cd frontend && npm install && npm run dev
Fehlerbehebung
API-Fehler / leere Seite
cd /opt/nginx_filebot
docker compose logs api --tail 80
curl -s http://localhost:8080/api/status | python3 -m json.tool
| Symptom | Ursache | Lösung |
|---|---|---|
relation "n8n.n8n_filebot" does not exist |
Falscher Tabellenname | table: "movie_files" in config.yaml |
relation "public...." does not exist |
Schema falsch | db_schema: "n8n" |
Spalte seen_at / cover_url fehlt |
Migration fehlt | SQL in sql/ ausführen |
API-Fehler: 503 |
DB nicht erreichbar | nc -zv <host> 5432 vom Server und aus Container |
| 0 Einträge | Filter zu streng / leere DB | Filter zurücksetzen, SELECT COUNT(*) prüfen |
| Altes Design | Cache / kein Rebuild | docker compose up -d --build web + Hard-Reload |
Netzwerk vom Container zur DB
docker compose exec api python -c "
import socket
s = socket.create_connection(('192.168.30.186', 5432), timeout=5)
print('OK'); s.close()
"
Port ändern
In docker-compose.yml z. B. "80:80" oder "8888:80" statt "8080:80".
Hinweise für n8n
- INSERT: Neue Zeilen haben
seen_at = NULL→ erscheinen als „neu“. - UPDATE:
seen_atundcover_urlnicht mit überschreiben, wenn der Status erhalten bleiben soll. - Datenbank-Workflow und Tabellenname (
movie_files, Scheman8n) mit derconfig.yamlabstimmen.
Lizenz / Nutzung
Privates Heimnetz-Projekt. Anpassungen nach Bedarf; config.yaml mit Passwörtern nicht in öffentliche Repositories committen.