- Port 8090 korrigiert
- Alle 4 Migrationen dokumentiert (inkl. cover_image/cover_mime_type)
- Neuen API-Endpunkt GET /records/{id}/cover ergänzt
- n8n-Workflows (Haupt + Backfill) mit Ablauf beschrieben
- Projektstruktur und deploy.sh aktualisiert
- Fehlerbehebungs-Tabelle erweitert
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 & Migrationen
- n8n Workflows
- Bedienung
- API-Referenz
- Projektstruktur
- Entwicklung & Updates
- Fehlerbehebung
Ü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 ist kein Node.js oder Python für den Betrieb nötig. Code anpassen, mit
scripts/deploy.shauf den Server übertragen, dortdocker compose up -d --buildausführen.
Für KI-Assistenten / Projekt-Kontext: siehe PROMPT.md.
Features
Medien-Übersicht
- Cover – primär aus der Datenbank (
cover_imageBYTEA), Fallback auf externe URL via Proxy - Original- und Neu-Dateiname immer sichtbar (Rename-Kontrolle)
- 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 Typ (Movie/Episode), Videoformat, Codec und Container
- 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
Weitere Aktionen
- Löschen einzelner Einträge (Mülleimer, mit Bestätigung)
- Live-Aktualisierung alle 5 Sekunden (pausiert im Hintergrund-Tab)
- Responsives Layout: Tabelle (Desktop) / Karten (Mobil, ≤900px)
Architektur
Browser
│
▼
┌─────────────────┐ /api/* ┌─────────────────┐
│ nginx (web) │ ───────────────►│ FastAPI (api) │
│ Port 8090 │ │ 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 |
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) - Alle SQL-Migrationen ausgeführt (siehe Datenbank & Migrationen)
Installation auf dem Server
Ausführliche Anleitung inkl. SMB-Gruppe und Umzug: docs/DEPLOY-NEUER-SERVER.md.
1. Projekt ablegen
sudo mkdir -p /opt/nginx_filebot
git clone https://git.vogt.de.com/vogto/filebot-media-browser.git /opt/nginx_filebot
2. Datenbank vorbereiten
Alle Migrationen ausführen (siehe Datenbank & Migrationen).
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:8090/api/health
# Erwartung: {"status":"ok"}
Im Browser: http://<Server-IP>:8090
Konfiguration
Datei: config/config.yaml (nicht versioniert, in .gitignore).
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) |
table |
Tabellenname innerhalb des Schemas |
db_schema |
Schema (typisch n8n) |
Datenbank & Migrationen
Tabelle
| Einstellung | Wert |
|---|---|
| Datenbank | n8n_filebot |
| Schema | n8n |
| Tabelle | movie_files |
| Vollqualifiziert | "n8n"."movie_files" |
Migrationen ausführen
Alle SQL-Dateien im Verzeichnis sql/ der Reihe nach ausführen:
cd /opt/nginx_filebot
for f in sql/00*.sql; do
echo "==> $f"
psql -h 192.168.30.186 -U n8n_filebot -d n8n_filebot -f "$f"
done
| Datei | Inhalt |
|---|---|
001_add_seen_at.sql |
Spalte seen_at TIMESTAMPTZ + Index |
002_add_cover_url.sql |
Spalte cover_url TEXT |
003_add_object_type.sql |
Spalte object_type TEXT |
004_add_cover_image.sql |
Spalten cover_image BYTEA + cover_mime_type TEXT |
Gesehen-Status
seen_at IS NULL→ ungesehen (blau hervorgehoben)- gesetzt → gesehen
Cover-Bilder
cover_url– externe Poster-URL (z. B. TVMaze, OMDB)cover_image– Bild-Binärdaten direkt in der DB (BYTEA)cover_mime_type– MIME-Typ des gespeicherten Bildes (z. B.image/jpeg)
Die UI lädt Cover primär aus der DB (/api/records/{id}/cover), Fallback auf den URL-Proxy (/api/cover?url=…).
n8n Workflows
Workflow 1: FileBot (Haupt-Workflow)
Wird von Filebot per Webhook ausgelöst und schreibt Medien-Metadaten in die DB. Danach läuft alle 2 Minuten ein Schedule-Trigger, der fehlende Cover-URLs nachlädt und die Cover-Bilder herunterlädt.
Ablauf:
Webhook (Filebot) → Insert rows (Metadaten)
Schedule (alle 2 Min) → Select (cover_url IS NULL)
→ Loop → Switch (Movie / Episode)
Movie → HTTP omdbapi → Edit Fields → Download Cover → Prepare Data → Speichere Cover in DB
Episode → If thetvdb_id → HTTP tvmaze → Edit Fields1 → Download Cover → Prepare Data → Speichere Cover in DB
Wichtig für n8n:
seen_atundcover_imagebeim INSERT/UPDATE nicht überschreibencover_urlbleibt immer erhalten (auch wenn der Bild-Download fehlschlägt)
Workflow 2: Cover Backfill
Einmaliger / regelmäßiger Workflow zum Nachfüllen von cover_image für Einträge, die bereits eine cover_url haben, aber noch kein Bild in der DB.
Ablauf:
Manuell / Stündlich
→ Select (cover_image IS NULL AND cover_url IS NOT NULL)
→ Loop (Batch 5)
→ HTTP Download (cover_url, continueOnFail)
→ Code (Base64 extrahieren)
→ Execute Query (UPDATE cover_image, cover_mime_type)
→ Loop (weiter)
Hinweis: Wenn der Workflow wegen Timeout abbricht (viele Einträge), beim erneuten Ausführen macht er automatisch dort weiter, wo er aufgehört hat – der SELECT lädt nur noch Einträge mit cover_image IS NULL.
Tabellengröße prüfen:
SELECT
COUNT(*) AS zeilen,
COUNT(cover_image) AS mit_bild,
pg_size_pretty(pg_total_relation_size('"n8n"."movie_files"')) AS gesamt
FROM n8n.movie_files;
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. Im Hintergrund-Tab pausiert das Polling.
- 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 gestapelt.
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: http://<host>:8090/api
| Methode | Pfad | Beschreibung |
|---|---|---|
| GET | /health |
DB-Ping |
| GET | /status |
Diagnose (Config, Tabellenliste bei Fehler) |
| GET | /columns |
Liste aller geladenen Spalten |
| GET | /filters |
Distinct-Werte für Filter-Buttons |
| GET | /records |
Datensätze; Query: search, format, codec, container, object_type, created_from, created_to, unseen_only, limit, offset |
| GET | /records/meta |
Leichtgewichtiger Check für Auto-Refresh (total, unseen_total, revision) |
| GET | /records/{id}/cover |
Cover-Bild direkt aus DB (BYTEA); 404 wenn kein Bild gespeichert |
| GET | /cover?url=… |
Cover-Proxy für externe URLs (umgeht Hotlink-Sperren) |
| PATCH | /records/{id}/seen |
Body: {"seen": true|false} |
| POST | /records/mark-all-seen |
Alle gefilterten Einträge als gesehen markieren |
| DELETE | /records/{id} |
Eintrag löschen |
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
│ ├── types.ts
│ ├── styles.css
│ ├── components/
│ │ ├── DataTable.vue
│ │ ├── MobileCards.vue
│ │ ├── FilterBar.vue
│ │ ├── CoverImage.vue # DB-Bild primär, URL-Proxy als Fallback
│ │ ├── NamePair.vue
│ │ ├── DateTimeRangeFilter.vue
│ │ └── DeleteButton.vue
│ ├── composables/
│ │ └── useAutoRefresh.ts
│ └── utils/
│ ├── coverUrl.ts
│ ├── datetime.ts
│ ├── names.ts
│ ├── objectType.ts
│ ├── preserveScroll.ts
│ └── seen.ts
├── nginx/
│ ├── Dockerfile
│ └── nginx.conf
├── scripts/
│ └── deploy.sh # Einmaliges SSH-Login, alle Dateien übertragen
└── sql/
├── 001_add_seen_at.sql
├── 002_add_cover_url.sql
├── 003_add_object_type.sql
└── 004_add_cover_image.sql
Entwicklung & Updates
Dateien auf den Server übertragen
cd "/Users/vogto/Documents/ClaudeCode/Webseite Filebot"
bash scripts/deploy.sh
Das Script baut die Container anschließend automatisch neu.
Manuell (einzelne Dateien)
cd /opt/nginx_filebot
docker compose up -d --build # alles
docker compose up -d --build web # nur Frontend
docker compose up -d --build api # nur API
Logs
docker compose logs -f api
docker compose logs -f web
Fehlerbehebung
API-Fehler / leere Seite
docker compose logs api --tail 80
curl -s http://localhost:8090/api/status | python3 -m json.tool
| Symptom | Ursache | Lösung |
|---|---|---|
relation "..." does not exist |
Falscher Tabellenname oder Schema | table: "movie_files", db_schema: "n8n" in config.yaml |
| Spalte fehlt (API 500) | Migration nicht ausgeführt | SQL in sql/ ausführen |
503 |
DB nicht erreichbar | nc -zv 192.168.30.186 5432 vom Server prüfen |
| Cover fehlt | cover_image IS NULL |
Backfill-Workflow in n8n ausführen |
| Altes Design | 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()
"