HousemannandClaude Sonnet 4.6 14c9bef002 Remove redundant FileBot_Main.json
FileBot_Cover_Backfill.json already contains the complete workflow
including webhook import, cover download and backfill logic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-17 20:36:06 +02:00
2026-07-17 18:54:54 +02:00
2026-07-17 20:36:06 +02:00

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.

Stack FastAPI PostgreSQL Docker


Inhaltsverzeichnis


Überblick

Die Anwendung läuft als zwei Docker-Container auf einem Linux-Server:

  1. web Nginx liefert die Vue-App aus und leitet /api an das Backend weiter
  2. 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.sh auf den Server übertragen, dort docker compose up -d --build ausführen.

Für KI-Assistenten / Projekt-Kontext: siehe PROMPT.md.


Features

Medien-Übersicht

  • Cover primär aus der Datenbank (cover_image BYTEA), 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_files in Datenbank n8n_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 NULLungesehen (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_at und cover_image beim INSERT/UPDATE nicht überschreiben
  • cover_url bleibt 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

  1. Seite öffnen → Daten werden geladen (Status „DB verbunden").
  2. Filter oben nutzen; Aktualisieren lädt neu, Zurücksetzen löscht Filter.
  3. Live-Aktualisierung: Alle 5 Sekunden werden die Daten still neu geladen. Im Hintergrund-Tab pausiert das Polling.
  4. Zeile antippen/klicken → Gesehen-Status umschalten.
  5. Mülleimer → Eintrag löschen (mit Bestätigung).
  6. 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()
"
S
Description
No description provided
Readme
151 KiB
Languages
Vue 56.4%
Python 25.1%
TypeScript 12%
CSS 3.1%
Shell 2.1%
Other 1.3%