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 (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 --build ausfü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_files in Datenbank n8n_filebot (anpassbar in Config)
  • Optional: Spalten seen_at und cover_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 NULLungesehen (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.pyCOLUMNS 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

  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 (ohne Seiten-Reload). Im Header: „Live · HH:MM:SS“. Im Hintergrund-Tab pausiert das Polling.
  4. Cover: Bilder werden über /api/cover geladen (Proxy), falls externe Poster (z. B. TVMaze) blockiert werden.
  5. Zeile antippen/klicken → Gesehen-Status umschalten.
  6. Mülleimer → Eintrag löschen (mit Bestätigung).
  7. 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_at und cover_url nicht mit überschreiben, wenn der Status erhalten bleiben soll.
  • Datenbank-Workflow und Tabellenname (movie_files, Schema n8n) mit der config.yaml abstimmen.

Lizenz / Nutzung

Privates Heimnetz-Projekt. Anpassungen nach Bedarf; config.yaml mit Passwörtern nicht in öffentliche Repositories committen.

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%