Files
filebot-media-browser/README.md
T
HousemannandClaude Sonnet 4.6 3f253ffe81 Initial commit: Filebot Media Browser
Vue 3 + FastAPI Webapp zur Anzeige von Filebot/n8n-Medienmetadaten aus PostgreSQL.
Enthält Frontend, API, Nginx-Config, SQL-Migrationen und Docker-Compose-Setup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-11 05:38:37 +02:00

481 lines
13 KiB
Markdown
Raw 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.
# 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](https://img.shields.io/badge/Vue-3-42b883) ![FastAPI](https://img.shields.io/badge/FastAPI-009688) ![PostgreSQL](https://img.shields.io/badge/PostgreSQL-336791) ![Docker](https://img.shields.io/badge/Docker-2496ED)
---
## Inhaltsverzeichnis
- [Überblick](#überblick)
- [Features](#features)
- [Architektur](#architektur)
- [Voraussetzungen](#voraussetzungen)
- [Installation auf dem Server](#installation-auf-dem-server)
- [Konfiguration](#konfiguration)
- [Datenbank](#datenbank)
- [Bedienung](#bedienung)
- [API-Referenz](#api-referenz)
- [Projektstruktur](#projektstruktur)
- [Entwicklung & Updates](#entwicklung--updates)
- [Fehlerbehebung](#fehlerbehebung)
- [Hinweise für n8n](#hinweise-für-n8n)
---
## Ü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`](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
```text
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](#datenbank))
---
## Installation auf dem Server
Ausführliche Anleitung inkl. **SMB-Gruppe** und Umzug: [`docs/DEPLOY-NEUER-SERVER.md`](docs/DEPLOY-NEUER-SERVER.md).
### 1. Projekt ablegen
```bash
# 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](#datenbank) Migrationen ausführen.
### 3. Konfiguration anlegen
```bash
cd /opt/nginx_filebot
cp config/config.example.yaml config/config.yaml
nano config/config.yaml
```
### 4. Starten
```bash
cd /opt/nginx_filebot
docker compose up -d --build
```
### 5. Prüfen
```bash
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):
```yaml
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:
```bash
docker compose exec api python /app/scripts/list-tables.py
```
### Migration: `seen_at`
```bash
psql -h 192.168.30.186 -U n8n_filebot -d n8n_filebot \
-f /opt/nginx_filebot/sql/001_add_seen_at.sql
```
Oder manuell:
```sql
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`
```bash
psql -h 192.168.30.186 -U n8n_filebot -d n8n_filebot \
-f /opt/nginx_filebot/sql/002_add_cover_url.sql
```
```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
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`
```json
{ "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:
```json
{
"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:
```json
{ "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
```text
.
├── 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
```bash
cd /opt/nginx_filebot
git pull # falls Git
docker compose up -d --build
```
Nur Frontend:
```bash
docker compose up -d --build web
```
Nur API:
```bash
docker compose up -d --build api
```
### Logs
```bash
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.
```bash
# 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
```bash
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
```bash
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.