1003 lines
37 KiB
Markdown
1003 lines
37 KiB
Markdown
# 🛠️ Kapitel 13 – Clipper (Tutorial)
|
||
|
||
---
|
||
|
||
## Einleitung
|
||
|
||
Clips sind der beste Weg, lange Streams in kurze, teilbare Highlights zu verwandeln. Unser Ziel in diesem Kapitel: Wir bauen ein System, das neue Videos automatisch erkennt, sinnvolle Stellen analysiert, passende Highlights schneidet und die Ergebnisse in Nextcloud ablegt – inklusive Titeln und Hashtags für jede Plattform. Der Clipper selbst übernimmt dabei die technische Verarbeitung, während **n8n** für Steuerung, Analyse und Benachrichtigungen sorgt. Das System bleibt dadurch flexibel, ressourcenschonend und jederzeit erweiterbar.
|
||
|
||
---
|
||
|
||
## Voraussetzungen
|
||
|
||
- **Proxmox LXC** mit Debian 12 (Bookworm)
|
||
- **Nextcloud** (Pflicht, Zielort für Clips & Metadaten)
|
||
- **n8n-Instanz** (Automatisierung, Steuerung Clipper, Analyse, Metadaten-Erzeugung)
|
||
- **Twitch-Entwickler-Account** inkl. API-Key (für VOD- und Clip-Zugriff)
|
||
- **Optional**: RTMP-Server, falls VODs lokal aufgezeichnet werden
|
||
- **Ressourcen für den LXC**: 1 vCPU, 1–2 GB RAM, 10 GB Speicher reichen aus
|
||
- **Grundwissen**: SSH-Verbindung, Nano-Editor, Basiskenntnisse in n8n
|
||
|
||
---
|
||
|
||
## Vorbereitung
|
||
|
||
Wir beginnen mit einem frischen Debian‑12‑LXC in Proxmox, benennen ihn `clipper` und vergeben die im Abschnitt oben genannten Ressourcen. Danach bringen wir das System auf Stand und installieren die Grundwerkzeuge:
|
||
```bash
|
||
apt update && apt upgrade -y
|
||
apt install -y curl unzip ffmpeg inotify-tools
|
||
```
|
||
|
||
Eine korrekte Systemzeit ist entscheidend, da Schnittmarken später auf exakten Sekunden basieren. Prüfe die Zeit mit:
|
||
```bash
|
||
timedatectl status
|
||
```
|
||
Wenn hier UTC steht und du lieber „Europe/Berlin“ nutzen willst:
|
||
```bash
|
||
timedatectl list-timezones | grep Europe
|
||
timedatectl set-timezone Europe/Berlin
|
||
timedatectl status
|
||
```
|
||
Die Zeit wird sofort angepasst, Logs und Schnittzeiten passen damit zur lokalen Umgebung.
|
||
|
||
Zum Schluss legen wir die Arbeitsordner an:
|
||
```bash
|
||
mkdir -p /srv/clipper/{watch,out,temp,logs}
|
||
```
|
||
- `watch` – Eingangsordner für neue Videos (egal ob von Twitch oder RTMP)
|
||
- `out` – fertige Clips und Metadaten
|
||
- `temp` – Zwischenspeicher für Analyse
|
||
- `logs` – Protokolle aller Abläufe
|
||
|
||
Damit ist das Fundament gelegt.
|
||
|
||
---
|
||
|
||
## Abschnitt 2 – Clipper‑LXC einrichten (Benutzer, Verzeichnisse, Pakete, Skripte)
|
||
|
||
In diesem Abschnitt richten wir den **Clipper‑Container** so ein, dass **SSH‑Schlüssel**, **Downloads** und **n8n‑Aufrufe** ohne Berechtigungsfehler funktionieren. Wir arbeiten jetzt **im Terminal deines Clipper‑LXC als root**.
|
||
|
||
> **Entscheidung & Begründung – Benutzer mit Home & Bash**
|
||
> Der Benutzer **clipper** bekommt ein **Homeverzeichnis** (`/home/clipper`) und eine **Login‑Shell**. So können SSH‑Schlüssel sauber in `~clipper/.ssh` landen und n8n später per SSH Befehle ausführen. Varianten ohne Home (System‑User) führen bei `ssh-copy-id` zu Fehlern.
|
||
|
||
### 2.1 Benutzer und Verzeichnisse anlegen (Terminal, als root)
|
||
|
||
```bash
|
||
adduser --home /home/clipper --shell /bin/bash clipper
|
||
```
|
||
Vergib ein Passwort und bestätige die Abfragen. Danach legst du die Arbeitsordner an und überträgst den Besitz an `clipper`:
|
||
```bash
|
||
mkdir -p /srv/clipper/{inbox,watch,out,temp,logs,bin}
|
||
chown -R clipper:clipper /srv/clipper
|
||
chmod 750 /srv/clipper
|
||
```
|
||
(optional, aber hilfreich für `ssh-copy-id` später):
|
||
```bash
|
||
install -d -m 700 -o clipper -g clipper /home/clipper/.ssh
|
||
```
|
||
|
||
> **Falls du den Benutzer bereits ohne Home angelegt hast:**
|
||
> Richte ihn so nach:
|
||
> `mkdir -p /home/clipper/.ssh && chown -R clipper:clipper /home/clipper && chmod 700 /home/clipper/.ssh`
|
||
|
||
### 2.2 Pakete installieren (Terminal, als root)
|
||
|
||
```bash
|
||
apt update && apt install -y yt-dlp jq python3 python3-venv curl unzip inotify-tools sudo
|
||
```
|
||
- **ffmpeg**: Analyse & Schnitt
|
||
- **yt-dlp**: Twitch‑VOD/Clip‑Downloads (HLS)
|
||
- **jq**: JSON‑Handling
|
||
- **python3/venv**: spätere Analyse‑Tools
|
||
- **inotify-tools**: Dateisystem‑Events (optional)
|
||
- **sudo**: für gezielte Rechteerhöhungen falls nötig
|
||
|
||
### 2.3 Zentrale Konfiguration (Terminal, als root)
|
||
|
||
Bevor wir die Umgebungsdatei anlegen, brauchen wir ein eigenes Konfigurationsverzeichnis. Das existiert standardmäßig nicht, daher legen wir es einmalig an:
|
||
|
||
```bash
|
||
mkdir -p /etc/clipper
|
||
chown root:clipper /etc/clipper
|
||
chmod 750 /etc/clipper
|
||
```
|
||
|
||
> **Entscheidung & Begründung – eigenes /etc/clipper**
|
||
> Konfiguration gehört nach `/etc`. Mit einem eigenen Ordner `/etc/clipper` bleibt alles übersichtlich getrennt.
|
||
> Besitzer ist `root`, die Gruppe `clipper`. So kann der Clipper-User die Datei lesen, aber nicht verändern – genau die Balance zwischen Sicherheit und Funktion.
|
||
|
||
|
||
|
||
Entscheidung & Begründung – eigenes /etc/clipper
|
||
Konfiguration gehört nach /etc. Mit einem eigenen Ordner /etc/clipper bleibt alles übersichtlich getrennt.
|
||
Besitzer ist root, die Gruppe clipper. So kann der Clipper-User die Datei lesen, aber nicht verändern – genau die Balance zwischen Sicherheit und Funktion.
|
||
|
||
Lege eine Umgebungsdatei an, die beide Skripte laden:
|
||
```bash
|
||
nano /etc/clipper/clipper.env
|
||
```
|
||
Inhalt:
|
||
```
|
||
SFTP_HOST=192.168.51.2
|
||
SFTP_PORT=22
|
||
SFTP_USER=sftp_uploader
|
||
SFTP_KEY=/home/clipper/.ssh/nc_sftp_ed25519
|
||
SFTP_DROP_BASE=incoming
|
||
```
|
||
Dateirechte setzen, damit `clipper` sie lesen darf:
|
||
```bash
|
||
chown root:clipper /etc/clipper/clipper.env
|
||
chmod 640 /etc/clipper/clipper.env
|
||
```
|
||
|
||
### 2.4 Python‑Umgebung vorbereiten (Wechsel zu Benutzer *clipper*)
|
||
|
||
Wechsle jetzt **zum Benutzer clipper**:
|
||
```bash
|
||
su - clipper
|
||
```
|
||
Erzeuge und fülle eine virtuelle Umgebung für die spätere Analyse:
|
||
```bash
|
||
python3 -m venv /srv/clipper/.venv
|
||
source /srv/clipper/.venv/bin/activate
|
||
pip install --upgrade pip
|
||
pip install numpy opencv-python-headless
|
||
deactivate
|
||
```
|
||
Wechsle für die nächsten Schritte **im Benutzer clipper** weiter.
|
||
|
||
### 2.5 Einstiegsskripte erstellen (im Benutzer *clipper*)
|
||
|
||
**Analyse‑Stub** – prüft Eingaben, schreibt Logs, erzeugt leere Kandidatenliste:
|
||
```bash
|
||
nano /srv/clipper/bin/clipper-analyze
|
||
```
|
||
Inhalt:
|
||
```bash
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
ENV_FILE="/etc/clipper/clipper.env"; [ -r "$ENV_FILE" ] || { echo "ENV nicht lesbar: $ENV_FILE" >&2; exit 1; }; source "$ENV_FILE"
|
||
IN="$1" # absolute Datei
|
||
JOBID="${2:-manual}"
|
||
mkdir -p "$CLIPPER_TMP/$JOBID"
|
||
echo "$(date '+%F %T') [ANALYZE] job=$JOBID file=$IN" | tee -a "$CLIPPER_LOG"
|
||
OUT_JSON="$CLIPPER_TMP/$JOBID/candidates.json"
|
||
echo '[]' > "$OUT_JSON"
|
||
echo "$OUT_JSON"
|
||
```
|
||
|
||
**Schneid‑Stub** – protokolliert Schnittaufrufe, echte Logik folgt in Abschnitt 5:
|
||
```bash
|
||
nano /srv/clipper/bin/clipper-cut
|
||
```
|
||
Inhalt:
|
||
```bash
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
ENV_FILE="/etc/clipper/clipper.env"; [ -r "$ENV_FILE" ] || { echo "ENV nicht lesbar: $ENV_FILE" >&2; exit 1; }; source "$ENV_FILE"
|
||
IN="$1" # absolute Datei
|
||
RANGES_JSON="$2" # Zeitbereiche (kommt später aus Abschnitt 4)
|
||
JOBID="${3:-manual}"
|
||
mkdir -p "$CLIPPER_OUT/$JOBID"
|
||
echo "$(date '+%F %T') [CUT] job=$JOBID file=$IN ranges=$RANGES_JSON" | tee -a "$CLIPPER_LOG"
|
||
exit 0
|
||
```
|
||
|
||
Rechte setzen und Eigentümer korrigieren:
|
||
```bash
|
||
chmod +x /srv/clipper/bin/clipper-*
|
||
chown -R clipper:clipper /srv/clipper/bin
|
||
```
|
||
|
||
### 2.6 Logrotation (zurück zu root)
|
||
|
||
Beende die Session (`exit`) und kehre zu **root** zurück. Richte Logrotation ein:
|
||
```bash
|
||
nano /etc/logrotate.d/clipper
|
||
```
|
||
Inhalt:
|
||
```
|
||
/srv/clipper/logs/*.log {
|
||
rotate 14
|
||
daily
|
||
missingok
|
||
notifempty
|
||
compress
|
||
delaycompress
|
||
copytruncate
|
||
}
|
||
```
|
||
|
||
> **Schnelltest (optional):**
|
||
> Zurück im **Benutzer clipper**:
|
||
> `/srv/clipper/bin/clipper-analyze /srv/clipper/watch/demo.mp4 job-001`
|
||
> `/srv/clipper/bin/clipper-cut /srv/clipper/watch/demo.mp4 /srv/clipper/temp/job-001/ranges.json job-001`
|
||
> `tail -n 50 /srv/clipper/logs/clipper.log`
|
||
|
||
Mit dieser Einrichtung sind **SSH‑Schlüssel**, **Berechtigungen** und **Pfade** konsistent. `ssh-copy-id` aus Abschnitt 3 funktioniert dadurch ohne Fehlermeldungen – und n8n kann die Skripte stabil starten.
|
||
|
||
---
|
||
|
||
## Abschnitt 3 – n8n ↔ Twitch: VOD & Clips importieren, in Nextcloud ablegen, Clipper starten
|
||
|
||
In diesem Abschnitt verbinden wir n8n mit Twitch, Nextcloud und dem Clipper. Das Ziel: n8n erkennt automatisch neue VODs auf Twitch, lädt sie zusammen mit Clips herunter, legt sie in Nextcloud ab und startet dann die Analyse auf dem Clipper. Wir gehen Schritt für Schritt vor – immer mit klaren Hinweisen, ob wir uns gerade in der **n8n-Weboberfläche**, im **Terminal** oder in **Nextcloud** befinden.
|
||
|
||
> [!NOTE]
|
||
> **Rollenverteilung**
|
||
> **n8n** steuert (APIs, Logik, Benachrichtigungen). **Clipper** arbeitet (Download, Analyse, Schnitt). **Nextcloud** speichert (Archiv & Übergabe). So bleibt n8n schlank und ausfallsicher, während Clipper CPU/IO für Medienjobs bekommt.
|
||
|
||
---
|
||
|
||
### Schritt 1: Zugriff zwischen den Containern vorbereiten (Terminal)
|
||
|
||
<details>
|
||
<summary>Optional: SSH Known-Hosts zurücksetzen (falls Clipper schon einmal erstellt wurde)</summary>
|
||
|
||
Wenn du den Clipper-LXC bereits früher erstellt und neu aufgesetzt hast, kann es zu einem SSH-Fehler kommen:
|
||
|
||
```
|
||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
|
||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||
```
|
||
|
||
Das bedeutet, dass sich der **Server-Hostkey** geändert hat. SSH blockiert dann aus Sicherheitsgründen.
|
||
|
||
#### Lösung Schritt für Schritt (im n8n-LXC als root)
|
||
|
||
1. **Fingerprint auf dem Clipper prüfen**
|
||
|
||
```bash
|
||
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
|
||
```
|
||
|
||
(SHA256-Wert merken und vergleichen)
|
||
|
||
2. **Alten Eintrag löschen**
|
||
|
||
```bash
|
||
ssh-keygen -R "<CLIPPER-IP>"
|
||
```
|
||
|
||
> [!NOTE]
|
||
> SSH schützt vor Man-in-the-Middle-Angriffen, indem es Hostkeys prüft. Wenn ein Container neu erstellt wird, ist ein neuer Hostkey normal. Wir löschen den alten Eintrag bewusst, prüfen den neuen Fingerprint und akzeptieren ihn erst danach. So bleibt die Verbindung sicher **und** stabil.
|
||
|
||
</details>
|
||
|
||
**SSH-Schlüssel für Clipper hinterlegen (zwingend):**
|
||
```bash
|
||
ssh-keygen -t rsa -b 4096 -m PEM -f ~/.ssh/id_n8n_clipper -N ""
|
||
ssh-copy-id -i ~/.ssh/id_n8n_clipper.pub clipper@<CLIPPER-IP> # << unbedingt ausführen
|
||
ssh -i ~/.ssh/id_n8n_clipper clipper@<CLIPPER-IP> "echo OK"
|
||
```
|
||
|
||
Ersetze `<CLIPPER-IP>` durch die IP/den Hostnamen deines Clipper-LXC (z. B. `10.0.0.42`). Wenn **OK** erscheint, kann n8n ohne Passwort auf den Clipper zugreifen.
|
||
|
||
> [!NOTE]
|
||
> RSA (PEM) statt ed25519: n8n akzeptiert nur PEM-Schlüssel zuverlässig. Ed25519 erzeugt oft OpenSSH-Keys, die von n8n nicht geparst werden können. Mit `-m PEM` stellen wir sicher, dass das Format passt.
|
||
|
||
---
|
||
|
||
### Nextcloud SFTP einrichten
|
||
|
||
WebDAV ist in Nextcloud eingebaut, aber bei großen Dateien wie VODs oft unzuverlässig. Gerade Streams oder lange Aufnahmen können abbrechen.
|
||
Darum richten wir hier eine stabile Alternative ein: SFTP mit eigenem Upload-Benutzer.
|
||
So landet jedes VOD automatisch in einem eigenen Sammelordner in Nextcloud – sauber sortiert, sicher übertragen und sofort sichtbar.
|
||
|
||
1. Logge dich in den Nextcloud Container per ssh ein:
|
||
2. Neuen SFTP-Benutzer anlegen
|
||
Mit den folgenden Befehlen legen wir uns den user an, der auf Linuxebene den Upload übernimmt.
|
||
Jetzt wird der ein oder andere fragen warum nicht mein Nextclouduser? Die Antwort darauf ist so einfach wie simpel. Wir arbeiten zwar im Nect
|
||
Container, aber nicht in Nextcloud, sondern auf Linux. Das heißt wir haben keinen Zugriff auf unseren Nextclouduser.
|
||
|
||
```bash
|
||
useradd -m -s /usr/sbin/nologin sftp_uploader
|
||
passwd -l sftp_uploader
|
||
```
|
||
|
||
Damit bekommt er ein Home-Verzeichnis, aber keine Shell. Passwort ist gesperrt – wir nutzen gleich Schlüssel.
|
||
|
||
3. Um eine unkomplizierte Verbindung zu garantieren nutzen wir statt der Passwort abfrage einen SSH Schlüssel. Das erhöht zusätzlich die Sicherheit.
|
||
Logge dich hierfür per SSH auf deinem Clipper LXC ein.
|
||
Mit `ssh-keygen -t ed25519 -C "sftp_uploader@nextcloud" -f ~/.ssh/nc_sftp_ed25519` erzeugen wir die nötigen Dateien und Schlüssel für die Verbindung.
|
||
Wenn du Enter drückst, legt er die Datei standardmäßig im Ordner ~/.ssh/ an.
|
||
Wenn nach einer Passphrase gefragt wird, einfach leer lassen (Enter). Damit kann Clipper später automatisiert hochladen.
|
||
Da wir den Key gleich benötigen, lässt du ihen dir mit `cat ~/.ssh/nc_sftp_ed25519.pub` anzeigen. Kopiere die gesamte Zeile. Sie sollte mit `ssh-ed25519` beginnen und mit `sftp_uploader@nextcloud enden`.
|
||
|
||
4. Wechsel nun wieder zurück zum Nextcloud LXC. Dort müssen wir zunächst einen Ordner anlegen in dem die Daten empfangen werden können.
|
||
Dies machen wir mit
|
||
```bash
|
||
sudo -u sftp_uploader mkdir -p ~sftp_uploader/.ssh
|
||
```
|
||
|
||
Mit dem nächsten Befehl legen wir entsprechend eine Datei die den erzeugten Schlüssel enthält.
|
||
```bash
|
||
nano /home/sftp_uploader/.ssh/authorized_keys
|
||
```
|
||
Füge die voerher kopierte Zeile ein, speichere die Datei mit `STRG + O ` ab und verlasse den Editor mit `STRG + X`.
|
||
|
||
Als nächstes gibst du
|
||
```bash
|
||
sudo chown -R sftp_uploader:sftp_uploader /home/sftp_uploader/.ssh
|
||
sudo chmod 700 /home/sftp_uploader/.ssh
|
||
sudo chmod 600 /home/sftp_uploader/.ssh/authorized_keys
|
||
```
|
||
|
||
ein. Hierdurch werden die Rechte korrekt gesetzt und die SFTP Verbindung wäre bereits möglich.
|
||
Als letztes müssen wir nur noch dafür sorgen, dass der Login fehlerfrei durchläuft. Öffne hierzu die `sshd_config` mit `nano /etc/ssh/sshd_config`.
|
||
Suche nach
|
||
`Subsystem sftp /usr/lib/openssh/sftp-server`
|
||
und ersetze sie mit
|
||
`Subsystem sftp internal-sftp`.
|
||
Nach dem Speicher mit `STRG + O` kannst du die Datei einfach mit `STRG + X` schließen und mit
|
||
`systemctl restart ssh`
|
||
ssh neustarten. Jetzt sind wir bereit für den ersten Test der Verbindung.
|
||
`sftp -i ~/.ssh/nc_sftp_ed25519 sftp_uploader@<IP_des_Nextcloud_LXC>` sollte
|
||
```bash
|
||
Connected to 192.168.51.2.
|
||
sftp>
|
||
```
|
||
anzeigen. Mit `quit`beendst du die Verbindung wieder.
|
||
|
||
Leider reicht dies jedoch noch nicht ganz und wir müssen ein paar weitere Schritte unternehmen.
|
||
|
||
5. Wir wären in der Lage jetzt bereits über die SFTP Verbindung Dateien zu senden. Allerdings würden sie nicht sichtbar für uns sein. Daher müssen wir zunächst einen weiteren User anlegen, der die Indizierung in der Nextcloud anstoßen darf.
|
||
Im Großen und Ganzen gehen wir zunächst genauso vor wie beim letzten User.
|
||
|
||
Logge dich hierzu zunächst wieder per ssh in deinen Nextcloud LXC ein.
|
||
|
||
```bash
|
||
ssh root@<IP_des_Nextcloud_LXC>
|
||
```
|
||
|
||
Dort legen wir nun unseren neuen Benutzer `nc_runner` an. Im Gegensatz zum SFTP Benutzer bekommt er diesmal eine Shell, damit er später auch Befehle ausführen kann. Ein Passwort vergeben wir trotzdem nicht, da wir wieder mit einem Schlüssel arbeiten werden.
|
||
|
||
```bash
|
||
useradd -m -s /bin/bash nc_runner
|
||
passwd -l nc_runner
|
||
```
|
||
|
||
Damit ist der Benutzer erstellt und bereit für den nächsten Schritt.
|
||
Nun wechseln wir auf unseren Clipper LXC. Dort erzeugen wir für den neuen Benutzer ein Schlüsselpaar, das für die Verbindung genutzt wird.
|
||
|
||
```bash
|
||
ssh-keygen -t ed25519 -C "nc_runner@nextcloud" -f ~/.ssh/nc_runner_ed25519
|
||
```
|
||
|
||
Wenn du Enter drückst, legt er die Datei standardmäßig im Ordner `~/.ssh/` an.
|
||
Wenn nach einer Passphrase gefragt wird, einfach leer lassen (Enter). Damit kann später n8n automatisiert die Indizierung auslösen.
|
||
|
||
Lasse dir nun den Public Key anzeigen und kopiere die gesamte Zeile.
|
||
|
||
```bash
|
||
cat ~/.ssh/nc_runner_ed25519.pub
|
||
```
|
||
|
||
Sie sollte mit `ssh-ed25519` beginnen und mit `nc_runner@nextcloud` enden.
|
||
|
||
Wechsel nun wieder zurück in den Nextcloud LXC. Dort legen wir auch für diesen Benutzer den `.ssh` Ordner an und tragen den eben kopierten Schlüssel in die `authorized_keys` Datei ein.
|
||
|
||
```bash
|
||
sudo -u nc_runner mkdir -p /home/nc_runner/.ssh
|
||
nano /home/nc_runner/.ssh/authorized_keys
|
||
```
|
||
|
||
Füge die kopierte Zeile ein, speichere die Datei mit `STRG + O`, bestätige mit Enter und verlasse den Editor mit `STRG + X`.
|
||
|
||
Damit die Datei auch wirklich korrekt funktioniert, setzen wir nun noch die Rechte:
|
||
|
||
```bash
|
||
chown -R nc_runner:nc_runner /home/nc_runner/.ssh
|
||
chmod 700 /home/nc_runner/.ssh
|
||
chmod 600 /home/nc_runner/.ssh/authorized_keys
|
||
```
|
||
|
||
Damit kann sich `nc_runner` bereits mit dem Schlüssel anmelden.
|
||
Damit er aber wirklich in der Lage ist die Indizierung anzustoßen, müssen wir ihm noch die Rechte geben, bestimmte Befehle als `www-data` auszuführen. Nur `www-data` darf nämlich das `occ`-Kommando in Nextcloud starten.
|
||
|
||
Dafür öffnen wir die sudo-Konfiguration:
|
||
|
||
```bash
|
||
visudo
|
||
```
|
||
|
||
Ganz am Ende fügen wir folgende Zeile ein:
|
||
|
||
```
|
||
nc_runner ALL=(www-data) NOPASSWD: /usr/bin/php, /var/www/nextcloud/occ
|
||
```
|
||
|
||
Hierdurch erlauben wir dem Benutzer `nc_runner`, genau diese Befehle als `www-data` auszuführen – und nichts anderes.
|
||
|
||
6. Nextcloud benötigt einen Cache, um effizient arbeiten zu können. Standardmäßig wird oft **APCu** vorgeschlagen, das in der Praxis aber gerade bei der Nutzung von `occ` über die Kommandozeile Probleme bereitet.
|
||
Darum setzen wir hier direkt auf **Redis**. Redis ist stabiler, von Nextcloud offiziell empfohlen und funktioniert sowohl für den Web- als auch den CLI-Zugriff.
|
||
|
||
Zuerst installieren wir Redis samt PHP-Anbindung. Logge dich hierfür in deinen Nextcloud LXC ein und führe aus:
|
||
|
||
```bash
|
||
apt-get update
|
||
apt-get install -y redis-server php-redis
|
||
```
|
||
|
||
Im Anschluss stellen wir Redis so ein, dass es über einen Socket und nicht über TCP angesprochen wird. Das ist schneller und sicherer, da der Zugriff nur lokal erfolgt. Öffne die Konfiguration mit:
|
||
|
||
```bash
|
||
nano /etc/redis/redis.conf
|
||
```
|
||
|
||
und passe die folgenden Zeilen an:
|
||
|
||
```
|
||
supervised no
|
||
#unixsocket /var/run/redis/redis-server.sock
|
||
#unixsocketperm 770
|
||
port 6379
|
||
```
|
||
Ersetze sie bzw. entfere die kommentierung, damit sie wie folgt aussehen:
|
||
```
|
||
supervised systemd
|
||
unixsocket /var/run/redis/redis-server.sock
|
||
unixsocketperm 770
|
||
port 0
|
||
```
|
||
|
||
Danach fügen wir den Webserver-Benutzer in die Redis-Gruppe hinzu und starten den Dienst neu:
|
||
|
||
```bash
|
||
usermod -aG redis www-data
|
||
systemctl restart redis
|
||
```
|
||
|
||
Jetzt müssen wir Nextcloud selbst mitteilen, dass es Redis nutzen soll. Öffne dazu die Datei config.php mit `nano /var/www/nextcloud/config/config.php` und ergänze bzw. ändere diese Einträge:
|
||
|
||
```php
|
||
'filelocking.enabled' => true,
|
||
'memcache.local' => '\OC\Memcache\Redis',
|
||
'memcache.locking' => '\OC\Memcache\Redis',
|
||
'redis' =>
|
||
array (
|
||
'host' => '/var/run/redis/redis-server.sock',
|
||
'port' => 0,
|
||
'timeout' => 1.5,
|
||
)
|
||
```
|
||
|
||
Speichern und schließen. Damit die Änderungen wirksam werden, starten wir noch PHP neu. Je nach Setup entweder mit:
|
||
|
||
```bash
|
||
systemctl restart apache2
|
||
```
|
||
|
||
Ab jetzt läuft Nextcloud vollständig mit Redis als Cache. Das heißt: `occ` funktioniert zuverlässig ohne zusätzliche Anpassungen, und wir sind bereit für den abschließenden Test im nächsten Schritt.
|
||
|
||
Zum Abschluss testen wir, ob alles funktioniert. Wechsle dazu wieder auf deinen Clipper LXC und rufe folgenden Befehl auf:
|
||
|
||
```bash
|
||
ssh -i ~/.ssh/nc_runner_ed25519 nc_runner@<IP_des_Nextcloud_LXC> "sudo -u www-data php /var/www/nextcloud/occ -V"
|
||
```
|
||
|
||
Wenn alles korrekt eingerichtet ist, bekommst du die aktuelle Nextcloud Version angezeigt.
|
||
|
||
|
||
---
|
||
|
||
### Twitch-API Zugang (Developer Console)
|
||
|
||
1. Gehe auf [Twitch Developer Console](https://dev.twitch.tv/console/apps) → **Applications** → **Register Your Application**.
|
||
2. Felder ausfüllen:
|
||
|
||
* **Name:** frei wählbar
|
||
* **OAuth Redirect URL:** z. B. `https://localhost/`
|
||
* **Category:** passend wählen
|
||
3. App speichern → **Manage** öffnen.
|
||
4. **Client ID** notieren, **New Secret** klicken und **Client Secret** sichern.
|
||
|
||
### Twitch OAuth2 Credential in n8n (Client Credentials Flow)
|
||
|
||
* **Name:** Twitch API
|
||
* **Grant Type:** `Client Credentials`
|
||
* **Authorization URL:** `https://id.twitch.tv/oauth2/authorize`
|
||
* **Access Token URL:** `https://id.twitch.tv/oauth2/token`
|
||
* **Client ID / Secret:** aus Twitch Developer Console
|
||
* **Scope:** leer lassen
|
||
* **Authentication:** `Body`
|
||
|
||
|
||
---
|
||
|
||
### Schritt 2: Twitch-User-ID herausfinden (n8n-Weboberfläche)
|
||
|
||
HTTP Request Node:
|
||
|
||
* Methode: GET
|
||
* URL:
|
||
|
||
```
|
||
https://api.twitch.tv/helix/users?login=<DEIN_LOGIN>
|
||
```
|
||
* Auth: OAuth2 Credential
|
||
* Header: `Client-Id: <DEINE_CLIENT_ID>`
|
||
|
||
Im Ergebnis findest du im Feld `data[0].id` deine **User-ID** (z. B. `123456789`).
|
||
|
||
---
|
||
|
||
### Schritt 3: Workflow bauen (n8n-Weboberfläche)
|
||
|
||
In diesem Schritt erstellen wir den eigentlichen Workflow in **n8n**. Er sorgt dafür, dass regelmäßig neue VODs bei Twitch abgefragt, geprüft und anschließend verarbeitet werden.
|
||
|
||
> [!NOTE]
|
||
> **Ziel dieses Schrittes**
|
||
> Wir bauen den Ablauf in n8n: Cron-Trigger → Twitch API → Prüfung der State-Datei → Entscheidung über neue VODs → Download und Upload in Nextcloud.
|
||
|
||
---
|
||
|
||
1. **Cron-Trigger – Zeitplan festlegen** (Node-Name: `Cron – Alle 10 Min`)
|
||
- Dieser Node löst den Workflow regelmäßig aus.
|
||
- Stelle ein: alle **10 Minuten** ausführen.
|
||
|
||
2. **HTTP Request – Get Videos** (Node-Name: `Get Twitch VOD IDs`)
|
||
- **Node-Typ:** HTTP Request
|
||
- **Methode:** GET
|
||
- **URL:** `https://api.twitch.tv/helix/videos?user_id=<DEINE_TWITCH_USER_ID>&type=archive&first=20`
|
||
- **Authentifizierung:** OAuth2 (Credential: *Twitch API*)
|
||
- **Header:** Client-Id: <DEINE_CLIENT_ID>
|
||
- **Response Format:** JSON **
|
||
|
||
2.1 **SSH Credentials in n8n anlegen**
|
||
- Damit n8n mit dem Clipper kommunizieren kann, brauchst du SSH-Zugangsdaten.
|
||
- Trage in n8n ein:
|
||
* Name: `SSH Clipper`
|
||
* Host: `<CLIPPER-IP>` (z. B. `10.0.0.42`)
|
||
* Port: `22`
|
||
* Username: `clipper`
|
||
* Private Key: Inhalt von `~/.ssh/id_n8n_clipper`
|
||
* Working Directory: `/srv/clipper`
|
||
|
||
3. **Split Out – vods** (Node-Name: `Split Out – Twitch VOD`)
|
||
- Node-Typ: Split Out
|
||
- Field to Split Out: `data`
|
||
|
||
4. **SSH Node – State-Datei prüfen** (Node-Name: `SSH – Check State`)
|
||
- Dieser Schritt prüft, ob die Datei `/srv/clipper/state/vod_seen.list` bereits existiert und ob VOD-IDs eingetragen sind.
|
||
- Command (als Expression):
|
||
```js
|
||
{{`set -euo pipefail; STATE_FILE="/srv/clipper/state/vod_seen.list"; fe=false; ne=false; arr='[]'; if [ -f "$STATE_FILE" ]; then fe=true; if [ -s "$STATE_FILE" ]; then ne=true; mapfile -t L < "$STATE_FILE"; json='['; sep=''; for id in "\${L[@]}"; do id_trim="$(printf '%s' "$id" | tr -d '\r' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"; [ -n "$id_trim" ] || continue; json+="$sep\"$id_trim\""; sep=','; done; json+=']'; arr="$json"; fi; fi; printf '{"file_exists":%s,"non_empty":%s,"vods":%s}\n' "$fe" "$ne" "$arr"`}}
|
||
```
|
||
- Typische Ergebnisse:
|
||
```json
|
||
{"file_exists":false,"non_empty":false,"vods":[]}
|
||
{"file_exists":true,"non_empty":false,"vods":[]}
|
||
{"file_exists":true,"non_empty":true,"vods":["123456789","987654321"]}
|
||
```
|
||
|
||
5. **Set – File Information** (Node-Name: `Set – File Information`)
|
||
- Node-Typ: Set
|
||
- Field: file_exists
|
||
- Type: Boolean
|
||
- Expression: `{{ JSON.parse($json.stdout).file_exists }}`
|
||
- Field: non_empty
|
||
- Type: Boolean
|
||
- Expression: `{{ JSON.parse($json.stdout).non_empty }}`
|
||
- Field: vods
|
||
- Type: Array
|
||
- Expression: `{{ JSON.parse($json.stdout).vods }} `
|
||
- Add Option: Ignore Type Conversion Errors -> "ON"
|
||
|
||
6. **Set – vods in Array** (Node-Name: `Set – vods in Array`)
|
||
- Node-Typ: Set
|
||
- Field: vods
|
||
- Type: Array
|
||
- Expression:
|
||
```js
|
||
{{ (typeof $json.vods === 'string' ? $json.vods : String($json.vods))
|
||
.split(',')
|
||
.map(s => s.trim())
|
||
.filter(Boolean) }}
|
||
```
|
||
|
||
7. **Split Out – vods** (Node-Name: `Split Out – vods`)
|
||
- Node-Typ: Split Out
|
||
- Field to Split Out: `vods`
|
||
|
||
8. **Vorbereitungen für VOD Download und Speicherung**
|
||
|
||
**8.1 Vorbereitung (einmalig) – SFTP-Verbindung testen
|
||
Ort: Clipper-LXC Shell
|
||
Wir prüfen, ob Clipper mit dem Key des sftp_uploader auf den Nextcloud-Host kommt und die Drop-Zone erreichbar ist.
|
||
```bash
|
||
su - clipper
|
||
sftp -i ~/.ssh/nc_sftp_ed25519 -oBatchMode=yes -oStrictHostKeyChecking=accept-new -P 22 sftp_uploader@<IP_des_Nextcloud_LXC> <<'SFTP'
|
||
mkdir incoming
|
||
mkdir incoming/_test
|
||
ls incoming
|
||
rmdir incoming/_test
|
||
SFTP
|
||
exit
|
||
```
|
||
Erwartung: die Ausgabe zeigt incoming. Dann ist die Verbindung korrekt.
|
||
|
||
**8.2 Download/Upload Skript erstellen**
|
||
**Ort:** Clipper-LXC Shell
|
||
|
||
Wir erstellen ein Skript, das ein Twitch-VOD mit yt-dlp lädt und per SFTP nach incoming/<VOD_ID>/<VOD_ID>.mp4 auf dem Nextcloud-Host hochlädt.
|
||
Voraussetzung: In /etc/clipper/clipper.env sind gesetzt:
|
||
SFTP_HOST, SFTP_PORT, SFTP_USER, SFTP_KEY, SFTP_DROP_BASE, außerdem CLIPPER_TMP, CLIPPER_OUT.
|
||
```bash
|
||
nano <clipper-ordner>/bin/clipper-vod-get
|
||
```
|
||
Nun befüllst du sie mit:
|
||
```bash
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
|
||
ENV_FILE="/etc/clipper/clipper.env"
|
||
[[ -r "$ENV_FILE" ]] || { echo "ENV nicht lesbar: $ENV_FILE" >&2; exit 1; }
|
||
source "$ENV_FILE"
|
||
|
||
ID="${1:?need VOD id}"
|
||
URL="${2:-https://www.twitch.tv/videos/${ID}}"
|
||
|
||
TMP="${CLIPPER_TMP:-/srv/clipper/temp}"
|
||
OUT_BASE="${CLIPPER_OUT:-/srv/clipper/out}/${ID}"
|
||
LOGDIR="/srv/clipper/logs/${ID}"
|
||
FILE="${TMP}/${ID}.mp4"
|
||
TEMP="${TMP}/${ID}.temp.mp4"
|
||
PART="${TMP}/${ID}.mp4.part"
|
||
LOCK="${TMP}/${ID}.lock"
|
||
|
||
DROP_BASE="${SFTP_DROP_BASE:-incoming}"
|
||
REMOTE_DIR="${DROP_BASE}/${ID}"
|
||
REMOTE_FILE="${REMOTE_DIR}/${ID}.mp4"
|
||
|
||
mkdir -p "$TMP" "$LOGDIR" "$OUT_BASE"
|
||
LOG="${LOGDIR}/download.log"
|
||
log(){ printf '[%(%F %T)T] %s\n' -1 "$*" ; }
|
||
exec > >(tee -a "$LOG") 2>&1
|
||
|
||
exec 9>"$LOCK"
|
||
if ! flock -n 9; then
|
||
log "LOCK: $ID wird bereits verarbeitet"
|
||
exit 0
|
||
fi
|
||
trap 'flock -u 9; rm -f "$LOCK"' EXIT
|
||
|
||
SFTP_OPTS=(-i "${SFTP_KEY}" -P "${SFTP_PORT:-22}" -oBatchMode=yes -oStrictHostKeyChecking=accept-new)
|
||
SFTP_TARGET="${SFTP_USER}@${SFTP_HOST}"
|
||
|
||
sftp_batch() {
|
||
local cmds
|
||
cmds=$(printf "%s\n" "$@")
|
||
sftp "${SFTP_OPTS[@]}" "${SFTP_TARGET}" <<< "$cmds"
|
||
}
|
||
|
||
exists_remote_file() {
|
||
local out
|
||
out=$(sftp "${SFTP_OPTS[@]}" "${SFTP_TARGET}" <<< "ls -l ${REMOTE_FILE}" 2>&1 || true)
|
||
[[ "$out" != *"No such file"* ]] && [[ "$out" != *"not found"* ]]
|
||
}
|
||
|
||
log "=== Start VOD ${ID} ==="
|
||
log "URL: ${URL}"
|
||
log "DROP: ${REMOTE_FILE}"
|
||
|
||
if exists_remote_file; then
|
||
log "SKIP: ${REMOTE_FILE} existiert bereits"
|
||
exit 0
|
||
fi
|
||
|
||
if [[ -s "$TEMP" && ! -s "$FILE" ]]; then
|
||
log "RESUME: $TEMP -> $FILE"
|
||
mv -f "$TEMP" "$FILE"
|
||
fi
|
||
|
||
OUT="${TMP}/${ID}.%(ext)s"
|
||
yt-dlp -q --no-progress --retries 20 --fragment-retries 50 --retry-sleep 5 \
|
||
--socket-timeout 30 --hls-prefer-ffmpeg --remux-video mp4 -o "$OUT" "$URL"
|
||
|
||
[[ -s "$FILE" ]] || { [[ -s "$TEMP" ]] && mv -f "$TEMP" "$FILE"; }
|
||
|
||
if [[ ! -s "$FILE" ]]; then
|
||
log "ERROR: Download fehlgeschlagen ($FILE fehlt/leer)"
|
||
exit 10
|
||
fi
|
||
|
||
sftp_batch "mkdir ${DROP_BASE}" "mkdir ${REMOTE_DIR}"
|
||
|
||
tries=0
|
||
until exists_remote_file; do
|
||
tries=$((tries+1))
|
||
log "UPLOAD Try #$tries: $FILE -> ${REMOTE_FILE}"
|
||
sftp_batch "reput ${FILE} ${REMOTE_FILE}" "put ${FILE} ${REMOTE_FILE}" || true
|
||
sleep $((2*tries))
|
||
[[ $tries -ge 5 ]] && break
|
||
done
|
||
|
||
if ! exists_remote_file; then
|
||
log "ERROR: Upload fehlgeschlagen"
|
||
exit 20
|
||
fi
|
||
|
||
rm -f "$PART" "$TEMP" || true
|
||
log "CLEANUP: $TMP"
|
||
rm -rf "${TMP:?}/"*
|
||
|
||
log "=== Done VOD ${ID} ==="
|
||
```
|
||
Mit diesem Skript laden wir die aktuellen VODs herunter, laden sie in die Nextcloud für die weitere Verabeitung und räumen wieder auf. Zusätzlich erzeugen wir logs in `<clipper-ordner>/logs/<ID>.log`.
|
||
|
||
Damit die Datei auch ausgeführt werden kann, musst du die folgenden zwei Befehle eingeben:
|
||
```bash
|
||
chmod 755 <clipper-ordner>/bin/clipper-vod-get
|
||
chown clipper:clipper <clipper-ordner>/bin/clipper-vod-get
|
||
```
|
||
> [!NOTE]
|
||
> Pro VOD entsteht ein Logfile in `<clipper-ordner>/logs/<ID>.log`. Du kannst es live mit `tail -f <clipper-ordner>/logs/<ID>.log` verfolgen.
|
||
|
||
8.3 **Finalize-Skript auf dem Nextcloud-Host**
|
||
Nachdem der Clipper die VOD-Datei in die Drop-Zone incoming/<VOD_ID> hochgeladen hat, müssen wir diese Dateien an den endgültigen Platz in Nextcloud verschieben. Nur so erscheinen sie auch im Web-Interface. Genau dafür legen wir jetzt ein Skript an, das automatisch aufgerufen werden kann.
|
||
|
||
Das Skript übernimmt drei Aufgaben:
|
||
- Dateien aus der Drop-Zone verschieben in den Zielordner innerhalb von Nextcloud.
|
||
- Rechte setzen, damit Nextcloud (Benutzer www-data) die Dateien verwalten kann.
|
||
- Den Nextcloud-Dateibaum mit occ files:scan aktualisieren, damit die Dateien sofort sichtbar werden.
|
||
|
||
Wie zuvor müssen wir ein Konfigurationsdatei anlegen.
|
||
```bash
|
||
nano /etc/nc_uploader.conf
|
||
```
|
||
Fülle sie mit:
|
||
```bash
|
||
NC_USER=DEIN_NC_USER # Nextcloud-Benutzer, dem die Dateien gehören sollen
|
||
NC_TARGET_SUBPATH="Medien/VODs" # Zielordner innerhalb von Nextcloud (wie er im Web erscheint)
|
||
NC_DATA="/var/www/nextcloud/data" # Basis-Datenverzeichnis deiner Nextcloud-Instanz
|
||
DROP_BASE="/home/sftp_uploader/incoming" # SFTP-Drop-Zone von sftp_uploader
|
||
PHP="/usr/bin/php"
|
||
OCC="/var/www/nextcloud/occ"
|
||
```
|
||
Speichere die Datei wieder mit `STRG + O` und schließe den Editor mit `STRG + x`.
|
||
|
||
Das folgende Skript sorgt dann dafür. dass unser eben hoch geladendes VOD an der richtigen Stelle zu finden ist und über die Weboberfläche erreichbar sein wird.
|
||
|
||
```bash
|
||
nano /usr/local/bin/nc_finalize_vod.sh
|
||
```
|
||
|
||
Inhalt:
|
||
```bash
|
||
# /usr/local/bin/nc_finalize_vod.sh
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
|
||
CONF="/etc/nc_uploader.conf"
|
||
[[ -f "$CONF" ]] || { echo "Config fehlt: $CONF" >&2; exit 1; }
|
||
# shellcheck disable=SC1090
|
||
source "$CONF"
|
||
|
||
VOD_ID="${1:?need VOD id}"
|
||
FILE_BASENAME="${2:-${VOD_ID}.mp4}"
|
||
|
||
SRC_DIR="${DROP_BASE}/${VOD_ID}"
|
||
SRC_FILE="${SRC_DIR}/${FILE_BASENAME}"
|
||
DST_DIR="${NC_DATA}/${NC_USER}/files/${NC_TARGET_SUBPATH}/${VOD_ID}"
|
||
SCAN_PATH="${NC_USER}/files/${NC_TARGET_SUBPATH}/${VOD_ID}"
|
||
|
||
mkdir -p "$DST_DIR"
|
||
|
||
if [[ -d "$SRC_DIR" ]]; then
|
||
# kompletter VOD-Ordner vorhanden → verschiebe Inhalt (oder Ordner, falls leer)
|
||
shopt -s nullglob dotglob
|
||
if compgen -G "${SRC_DIR}/*" > /dev/null; then
|
||
mv -f "${SRC_DIR}/"* "$DST_DIR"/
|
||
fi
|
||
rmdir "$SRC_DIR" 2>/dev/null || true
|
||
elif [[ -f "$SRC_FILE" ]]; then
|
||
mv -f "$SRC_FILE" "$DST_DIR"/
|
||
else
|
||
echo "Quelle nicht gefunden: $SRC_DIR oder $SRC_FILE" >&2
|
||
exit 2
|
||
fi
|
||
|
||
chown -R www-data:www-data "$DST_DIR"
|
||
"$PHP" "$OCC" files:scan --path="$SCAN_PATH" --quiet
|
||
|
||
echo "OK: ${DST_DIR}"
|
||
```
|
||
Wie zuvor auch, müssen wir die Rechte korrekt setzen, damit alles reibungslos funktioniert.
|
||
Gebe dazu in der Konsole
|
||
```bash
|
||
chmod 755 /usr/local/bin/nc_finalize_vod.sh
|
||
chown root:root /usr/local/bin/nc_finalize_vod.sh
|
||
```
|
||
ein.
|
||
|
||
Im weiteren Verlauf erstellen wir dann einen Node, der dieses Skript aufruft. Im Anschluss sollte das VOD in dem entsprechenden Ordner landen.
|
||
|
||
9. **Merge – Combine** (Node-Name: `Select VODs to Download`)
|
||
- **Node-Typ:** Merge
|
||
- **Mode:** Combine
|
||
- **Combine By:** Matching Fields
|
||
- **Fields To Match Have Different Names**: "ON"
|
||
- **Eingang 1:** Split Out – vods
|
||
- **Eingang 2:** Split Out – Twitch VOD
|
||
- **Input 1 Field**: vods
|
||
- **Input 2 Field**: data.id
|
||
- **Output Type**: Keep Non-Matches
|
||
- **Output Data From**: Input 2
|
||
Mit diesem Merge Node sorgen wir dafür, dass wir nur die VODs herunter laden, die neu sind und noch nicht von Clipper bearbeitet wurden.
|
||
|
||
10. **Split In Batches** (Node-Name: `Einzeldurchlauf`)
|
||
- **Node-Typ:** Split In Batches
|
||
- **Batch Size:** 1
|
||
Dieser Node sorgt dafür, dass wenn mal mehr wie ein VOD heruntergaladen werden muss dies nicht parallel geschieht. S sparen wir Ressourcen und sind schneller mit der Arbeit fertig.
|
||
|
||
11. **SSH Node 1 – State-Datei schreiben** (Node-Name: `State Datei schreiben`)
|
||
- **Node-Typ:** SSH
|
||
- **Credentials:** *SSH Clipper*
|
||
- **Operation:** Execute Command
|
||
- **Command is an Expression:** **ON**
|
||
- **Command:**
|
||
``` bash
|
||
{{`set -euo pipefail; STATE="/srv/clipper/state/vod_seen.list"; mkdir -p "$(dirname "$STATE")"; if [ -s "$STATE" ]; then printf "%s\n" "${$json.data.id}" >> "$STATE"; else printf "%s\n" "${$json.data.id}" > "$STATE"; fi`}}
|
||
```
|
||
|
||
12. **SSH Node 2 – Download & Upload** (Node-Name: `Down 'n' Up`)
|
||
- **Node-Typ:** SSH
|
||
- **Credentials:** *SSH Clipper*
|
||
- **Operation:** Execute Command
|
||
- **Command is an Expression:** **ON**
|
||
- **Command:**
|
||
```bash
|
||
<clipper-ordner>/bin/clipper-vod-get "{{$('Merge').item.json.data.id}}" "{{ $json.url || ('https://www.twitch.tv/videos/' + $('Merge').item.json.data.id) }}"
|
||
```
|
||
|
||
13. **SSH Node 3 – Finalize** (Node-Name: Finalize VOD)
|
||
- Node-Typ: SSH
|
||
- Credentials: SSH Nextcloud (nc_runner)
|
||
- Operation: Execute Command
|
||
- Command is an Expression: ON
|
||
- Command:
|
||
```bash
|
||
/usr/local/bin/nc_finalize_vod.sh {{$('Down 'n' Up').item.json.vodId}} {{$('Down 'n' Up').item.json.filename}}
|
||
```
|
||
|
||
Diese 12 Nodes werden das gesamte Grundgerüst der gesamten Automation sein. Wie aber müssen sie verbudnen werden?
|
||
Das folgende Schaubild zeigt dir die konkrete Verkabelung
|
||
|
||
```bash
|
||
--- **SSH – Check State** --- **Set – File Information** --- **Set – vods in Array** --- **Split Out – vods** ------
|
||
| |
|
||
**Cron – Alle 10 Min** ---- | **Select VODs to Download** --- **Einzeldurchlauf** --- **State Datei schreiben** --- **Down 'n' Up** --- **Finalize VOD** (Hier folgen später weitere Nodes, aber da der Einzeldurchlauf ein Loop ist wird der letzte Node mit Einzeldurchlauf verbunden)
|
||
| |
|
||
--- *Get Twitch VOD IDs* --- *Split Out* – *Twitch VOD* --------------------------------------------------
|
||
```
|
||
|
||
|
||
# 🧪 Abschnitt 4 – Analyse
|
||
|
||
|
||
---
|
||
|
||
|
||
> [!NOTE]
|
||
> Wir verwenden in diesem Tutorial **keine KI‑Analyse**. Hierfür wird es ein **eigenes Tutorial** geben. Wir legen aber bereits alles so an, dass später keine Umbauten mehr nötig sind.
|
||
|
||
|
||
---
|
||
|
||
|
||
## Schritt 4.1 – Analyse‑Skript anlegen
|
||
**Ort:** Terminal im **Clipper‑LXC** → als Benutzer **`clipper`**
|
||
|
||
|
||
Öffne das Skript und trage den Inhalt ein:
|
||
|
||
|
||
```bash
|
||
nano /srv/clipper/bin/vod-analyze
|
||
```
|
||
|
||
|
||
Inhalt:
|
||
```bash
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
. /etc/clipper/clipper.env
|
||
|
||
|
||
ID="${1:?need VOD id}"
|
||
|
||
|
||
VOD_IN_MP4="${CLIPPER_IN}/${ID}.mp4"
|
||
OUT_BASE="${CLIPPER_OUT}/${ID}"
|
||
ANALYSIS="${OUT_BASE}/analysis"
|
||
LOGDIR="${CLIPPER_LOG}/${ID}"
|
||
|
||
|
||
mkdir -p "$ANALYSIS" "$LOGDIR"
|
||
exec > >(tee -a "${LOGDIR}/analyze.log") 2>&1
|
||
echo "== Analyze $ID =="
|
||
|
||
|
||
# 1) Szenenwechsel
|
||
ffmpeg -hide_banner -loglevel error -i "${VOD_IN_MP4}" \
|
||
-vf "scale=-2:360,select=gt(scene\,0.30),showinfo" -an -f null - \
|
||
2> "${LOGDIR}/sceneinfo.log"
|
||
|
||
|
||
# 2) Audio-Statistik
|
||
ffmpeg -hide_banner -loglevel error -i "${VOD_IN_MP4}" \
|
||
-vn -ac 1 -ar 16000 \
|
||
-af "astats=metadata=1:reset=2,ametadata=print:key=lavfi.astats.Overall.RMS_level" \
|
||
-f null - \
|
||
2> "${LOGDIR}/astats.log" || true
|
||
|
||
|
||
# 3) Logs → candidates.json
|
||
ANALYSIS="$ANALYSIS" LOGDIR="$LOGDIR" python3 - <<'PY'
|
||
import os,re,json
|
||
out=os.environ["ANALYSIS"]; log=os.environ["LOGDIR"]
|
||
|
||
|
||
scene_ts=[]
|
||
with open(os.path.join(log,"sceneinfo.log"), errors="ignore") as f:
|
||
for line in f:
|
||
m=re.search(r"pts_time:([0-9]+(?:\.[0-9]+)?)", line)
|
||
if m: scene_ts.append(float(m.group(1)))
|
||
|
||
|
||
has_audio=False
|
||
ap=os.path.join(log,"astats.log")
|
||
if os.path.exists(ap):
|
||
with open(ap, errors="ignore") as f:
|
||
has_audio = "RMS_level" in f.read()
|
||
|
||
|
||
cands=[{
|
||
"start": max(0.0,t-2.0),
|
||
"end": t+6.0,
|
||
"score": round(0.6+(0.1 if has_audio else 0),2),
|
||
"tags": ["scene-cut"] + (["audio-peak"] if has_audio else [])
|
||
} for t in scene_ts]
|
||
|
||
|
||
with open(os.path.join(out,"candidates.json"),"w",encoding="utf-8") as f:
|
||
json.dump(cands,f,ensure_ascii=False,indent=2)
|
||
print("Wrote", os.path.join(out,"candidates.json"))
|
||
PY
|
||
|
||
|
||
echo "== Done $ID =="
|
||
```
|
||
|
||
|
||
Ausführbar machen:
|
||
```bash
|
||
chmod +x /srv/clipper/bin/clipper-analyze
|
||
```
|
||
|
||
|
||
---
|
||
|
||
|
||
## Schritt 4.2 – n8n: Analyse starten
|
||
**Ort:** n8n‑Weboberfläche
|
||
|
||
|
||
**SSH Node – Analyze VOD**
|
||
- **Credentials:** `SSH Clipper`
|
||
- **Working Dir:** `/srv/clipper`
|
||
- **Command (Expression):**
|
||
```js
|
||
{{`/srv/clipper/bin/vod-analyze ${$json.data.id}`}}
|
||
```
|
||
|
||
|
||
---
|
||
|
||
|
||
## Ergebnis
|
||
- Für jedes neue VOD wird automatisch ein eigener Log‑Ordner erstellt.
|
||
- Die Analyse erzeugt pro VOD eine **`candidates.json`** mit Zeitfenstern.
|
||
- Abschnitt 5 nutzt diese JSON für den Schnitt.
|