Kapitel 13/Tutorial.md aktualisiert
This commit is contained in:
@@ -222,7 +222,7 @@ Mit dieser Einrichtung sind **SSH‑Schlüssel**, **Berechtigungen** und **Pfade
|
|||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
> [!HINWEIS]
|
> [!NOTE]
|
||||||
> **Rollenverteilung**
|
> **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.
|
> **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.
|
||||||
|
|
||||||
@@ -259,7 +259,7 @@ ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
|
|||||||
ssh-keygen -R "<CLIPPER-IP>"
|
ssh-keygen -R "<CLIPPER-IP>"
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Entscheidung & Begründung – Sicherheitsprinzip**
|
> [!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.
|
> 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>
|
</details>
|
||||||
@@ -273,8 +273,8 @@ 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.
|
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.
|
||||||
|
|
||||||
> **Entscheidung & Begründung – RSA (PEM) statt ed25519**
|
> [!NOTE]
|
||||||
> 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.
|
> 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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -319,6 +319,7 @@ https://<DEINE_NEXTCLOUD_DOMAIN>/remote.php/dav/files/<DEIN_NC_BENUTZER>/
|
|||||||
* **Scope:** leer lassen
|
* **Scope:** leer lassen
|
||||||
* **Authentication:** `Body`
|
* **Authentication:** `Body`
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Schritt 2: Twitch-User-ID herausfinden (n8n-Weboberfläche)
|
### Schritt 2: Twitch-User-ID herausfinden (n8n-Weboberfläche)
|
||||||
@@ -340,315 +341,221 @@ Im Ergebnis findest du im Feld `data[0].id` deine **User-ID** (z. B. `123456789`
|
|||||||
|
|
||||||
### Schritt 3: Workflow bauen (n8n-Weboberfläche)
|
### Schritt 3: Workflow bauen (n8n-Weboberfläche)
|
||||||
|
|
||||||
1. **Cron-Trigger:** alle 10 Minuten
|
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.
|
||||||
|
|
||||||
2. **HTTP Request (Get Videos):**
|
> [!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 – Twitch Videos abfragen** (Node-Name: `HTTP – Get Videos`)
|
||||||
|
- Dieser Node ruft über die Twitch-API alle VODs deines Accounts ab.
|
||||||
|
- URL:
|
||||||
```
|
```
|
||||||
https://api.twitch.tv/helix/videos?user_id=<DEINE_TWITCH_USER_ID>&type=archive
|
https://api.twitch.tv/helix/videos?user_id=<DEINE_TWITCH_USER_ID>&type=archive
|
||||||
```
|
```
|
||||||
|
- Authentifizierung: OAuth2 (Credential „Twitch API“)
|
||||||
|
- Header: `Client-Id: <DEINE_CLIENT_ID>`
|
||||||
|
|
||||||
3. **SSH Credentials in n8n anlegen**
|
3. **SSH Credentials in n8n anlegen**
|
||||||
Damit n8n mit dem Clipper arbeiten kann, legen wir zunächst ein SSH Credential an:
|
- Damit n8n mit dem Clipper kommunizieren kann, brauchst du SSH-Zugangsdaten.
|
||||||
|
- Trage in n8n ein:
|
||||||
* **Name:** SSH Clipper
|
* Name: `SSH Clipper`
|
||||||
* **Host:** `<CLIPPER-IP>` (z. B. `10.0.0.42`)
|
* Host: `<CLIPPER-IP>` (z. B. `10.0.0.42`)
|
||||||
* **Port:** 22
|
* Port: `22`
|
||||||
* **Username:** `clipper`
|
* Username: `clipper`
|
||||||
* **Private Key:** Inhalt von `~/.ssh/id_n8n_clipper` (PEM-Format)
|
* Private Key: Inhalt von `~/.ssh/id_n8n_clipper`
|
||||||
* **Working Directory:** `/srv/clipper`
|
* Working Directory: `/srv/clipper`
|
||||||
|
|
||||||
4. **SSH Node – State-Datei prüfen**
|
|
||||||
Jetzt prüfen wir, ob die Datei `/srv/clipper/state/vod_seen.list` existiert und ob bereits VOD-IDs darin stehen.
|
|
||||||
|
|
||||||
**Command (als Expression):**
|
|
||||||
|
|
||||||
|
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
|
```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"`}}
|
{{`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:
|
||||||
**Beispiele für den Output:**
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{"file_exists":false,"non_empty":false,"vods":[]}
|
{"file_exists":false,"non_empty":false,"vods":[]}
|
||||||
{"file_exists":true,"non_empty":false,"vods":[]}
|
{"file_exists":true,"non_empty":false,"vods":[]}
|
||||||
{"file_exists":true,"non_empty":true,"vods":["123456789","987654321"]}
|
{"file_exists":true,"non_empty":true,"vods":["123456789","987654321"]}
|
||||||
```
|
```
|
||||||
5. ** Set Node – Felder übernehmen **
|
|
||||||
|
|
||||||
### Set Node – Felder übernehmen
|
5. **Set Node – Ergebnisse übernehmen** (Node-Name: `Set – State Ergebnis`)
|
||||||
|
- Ziel: die Ausgabe aus dem vorherigen Schritt in Felder umwandeln, die n8n versteht.
|
||||||
file_exists → ```json {{ JSON.parse($json.stdout).file_exists }} ```
|
- Expressions:
|
||||||
non_empty → ```json {{ JSON.parse($json.stdout).non_empty }} ```
|
* file_exists → `{{ JSON.parse($json.stdout).file_exists }}`
|
||||||
vods → ```json {{ JSON.parse($json.stdout).vods }} ```
|
* non_empty → `{{ JSON.parse($json.stdout).non_empty }}`
|
||||||
|
* vods → `{{ JSON.parse($json.stdout).vods }}`
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> Stelle die Felder auf den Typ Array bzw. Boolean um.
|
> Hier nur die Expressions eintragen, keine Typ-Konvertierungen vornehmen.
|
||||||
> Aktiviere die Option: Add Option → Ignore Type Conversion Errors → einschalten.
|
|
||||||
|
|
||||||
|
6. **If-Node – Entscheidung** (Node-Name: `If – State Prüfung`)
|
||||||
6. ** If-Node – zwei klare Wege **
|
- Dieser Node entscheidet, ob neue VODs heruntergeladen werden müssen.
|
||||||
|
- Bedingungen:
|
||||||
In diesem Schritt legen wir die Entscheidungspfade in einem **If-Node** an. Dafür werden zwei Bedigungen hinzugefügt, die jeweils einen klar benannten Output erzeugen.
|
|
||||||
|
|
||||||
### If-Node Einstellungen
|
|
||||||
```git
|
|
||||||
1. Bedingung: {{ $json.file_exists }} is true
|
|
||||||
- Field type: Boolean
|
|
||||||
|
|
||||||
2. Bedingung: {{ $json.non_empty }} is true
|
|
||||||
- Field type: Boolean
|
|
||||||
|
|
||||||
3. Operator: AND
|
|
||||||
```
|
```
|
||||||
Damit hat der if-Node zwei klar angesprochene Ausgänge, die die weitere Logik steuern.
|
1. Bedingung: {{ $json.file_exists }} is true (Boolean)
|
||||||
|
2. Bedingung: {{ $json.non_empty }} is true (Boolean)
|
||||||
7. ** False - Download **
|
Operator: AND
|
||||||
Ziel: n8n erkennt neue VODs, erstellt/aktualisiert die State-Datei und lädt jedes VOD **sequenziell** im Clipper herunter und direkt in **Nextcloud** hoch (mit automatischem Aufräumen und Logfiles). Alles wird von n8n gesteuert, die Dateiübertragung übernimmt **rclone** im Clipper.
|
```
|
||||||
|
- Ergebnis: zwei mögliche Pfade → False = neu anlegen, True = prüfen auf neue VODs.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**A. Vorbereitung (einmalig) – rclone an Nextcloud anbinden**
|
7. **False-Pfad – VODs herunterladen und hochladen**
|
||||||
Ort: *Clipper‑LXC Shell*
|
|
||||||
|
|
||||||
1) rclone installieren (als root)
|
> [!NOTE]
|
||||||
|
> Dieser Pfad läuft, wenn die State-Datei nicht existiert oder leer ist. Es werden alle VODs heruntergeladen und in Nextcloud hochgeladen.
|
||||||
|
|
||||||
|
**A. Vorbereitung (einmalig) – rclone an Nextcloud anbinden**
|
||||||
|
**Ort:** Clipper-LXC Shell
|
||||||
```bash
|
```bash
|
||||||
apt update && apt install -y rclone
|
apt update && apt install -y rclone
|
||||||
```
|
|
||||||
2) Zum Benutzer **clipper** wechseln
|
|
||||||
```bash
|
|
||||||
su - clipper
|
su - clipper
|
||||||
```
|
|
||||||
3) Remote **nc** anlegen (Passwort wird automatisch obskuriert)
|
|
||||||
```bash
|
|
||||||
rclone config create nc webdav \
|
rclone config create nc webdav \
|
||||||
url=https://DEINE_DOMAIN/remote.php/dav/files/DEIN_BENUTZERNAME/ \
|
url=https://DEINE_DOMAIN/remote.php/dav/files/DEIN_BENUTZERNAME/ \
|
||||||
vendor=nextcloud \
|
vendor=nextcloud \
|
||||||
user=DEIN_BENUTZERNAME \
|
user=DEIN_BENUTZERNAME \
|
||||||
pass=$(rclone obscure 'DEIN_APP_PASSWORT')
|
pass=$(rclone obscure 'DEIN_APP_PASSWORT')
|
||||||
```
|
|
||||||
4) Verbindung testen
|
|
||||||
```bash
|
|
||||||
rclone ls nc: --config /home/clipper/.config/rclone/rclone.conf
|
rclone ls nc: --config /home/clipper/.config/rclone/rclone.conf
|
||||||
```
|
```
|
||||||
> **Entscheidung & Begründung**
|
> [!NOTE]
|
||||||
> Uploads laufen im Clipper mit rclone (WebDAV). Das ist robuster als Upload‑Nodes in n8n, vermeidet Binary‑Fehler und räumt lokal automatisch auf.
|
> Uploads laufen im Clipper mit rclone. Das ist robuster als Upload-Nodes in n8n, vermeidet Fehler und räumt lokale Dateien auf.
|
||||||
|
|
||||||
---
|
**B. Download/Upload Skript erstellen**
|
||||||
|
**Ort:** Clipper-LXC Shell
|
||||||
**B. Download/Upload als Skript (übersichtlich & mit Logs)**
|
|
||||||
Ort: *Clipper‑LXC Shell*
|
|
||||||
|
|
||||||
1) Skript anlegen
|
|
||||||
```bash
|
```bash
|
||||||
nano <clipper-ordner>/bin/clipper-vod-get
|
nano <clipper-ordner>/bin/clipper-vod-get
|
||||||
```
|
```
|
||||||
|
Inhalt:
|
||||||
2) Inhalt einfügen → speichern (**Ctrl+O**, Enter) → schließen (**Ctrl+X**)
|
```bash
|
||||||
```js
|
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
ID="${1:?need VOD id}"
|
ID="${1:?need VOD id}"
|
||||||
URL="${2:-https://www.twitch.tv/videos/${ID}}"
|
URL="${2:-https://www.twitch.tv/videos/${ID}}"
|
||||||
|
|
||||||
TMP="<clipper-ordner>/temp"
|
TMP="<clipper-ordner>/temp"
|
||||||
LOGDIR="<clipper-ordner>/logs"
|
LOGDIR="<clipper-ordner>/logs"
|
||||||
CONF="/home/clipper/.config/rclone/rclone.conf"
|
CONF="/home/clipper/.config/rclone/rclone.conf"
|
||||||
DST="nc:<gewünschter Ordner>/VODs/${ID}/"
|
DST="nc:<gewünschter Ordner>/VODs/${ID}/"
|
||||||
|
|
||||||
OUT="$TMP/${ID}.%(ext)s"
|
OUT="$TMP/${ID}.%(ext)s"
|
||||||
FILE="$TMP/${ID}.mp4"
|
FILE="$TMP/${ID}.mp4"
|
||||||
TEMP="$TMP/${ID}.temp.mp4"
|
TEMP="$TMP/${ID}.temp.mp4"
|
||||||
PART="$TMP/${ID}.mp4.part"
|
PART="$TMP/${ID}.mp4.part"
|
||||||
LOCK="$TMP/${ID}.lock"
|
LOCK="$TMP/${ID}.lock"
|
||||||
LOG="$LOGDIR/${ID}.log"
|
LOG="$LOGDIR/${ID}.log"
|
||||||
|
|
||||||
mkdir -p "$TMP" "$LOGDIR"
|
mkdir -p "$TMP" "$LOGDIR"
|
||||||
|
log() { echo "[$(date '+%F %T')] $*" | tee -a "$LOG"; }
|
||||||
log() {
|
|
||||||
echo "[$(date '+%F %T')] $*" | tee -a "$LOG"
|
|
||||||
}
|
|
||||||
|
|
||||||
log "=== Start VOD $ID ==="
|
log "=== Start VOD $ID ==="
|
||||||
log "URL: $URL"
|
log "URL: $URL"
|
||||||
|
if rclone lsf "$DST" --config "$CONF" >/dev/null 2>&1; then log "SKIP: $ID bereits in Nextcloud"; exit 0; fi
|
||||||
if rclone lsf "$DST" --config "$CONF" >/dev/null 2>&1; then
|
if [[ -e "$LOCK" ]]; then log "LOCK: $ID wird bereits verarbeitet"; exit 0; fi
|
||||||
log "SKIP: $ID bereits in Nextcloud"
|
trap 'rm -f "$LOCK"' EXIT; : > "$LOCK"
|
||||||
exit 0
|
if [[ -s "$TEMP" && ! -s "$FILE" ]]; then mv -f "$TEMP" "$FILE"; fi
|
||||||
fi
|
if [[ -s "$FILE" ]]; then rclone move "$FILE" "$DST" --config "$CONF" --create-empty-src-dirs -v; rm -f "$PART" "$TEMP" || true; exit 0; fi
|
||||||
|
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"
|
||||||
if [[ -e "$LOCK" ]]; then
|
|
||||||
log "LOCK: $ID wird bereits verarbeitet"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
trap 'rm -f "$LOCK"' EXIT
|
|
||||||
: > "$LOCK"
|
|
||||||
|
|
||||||
if [[ -s "$TEMP" && ! -s "$FILE" ]]; then
|
|
||||||
log "RESUME: TEMP gefunden → umbenennen"
|
|
||||||
mv -f "$TEMP" "$FILE"
|
|
||||||
fi
|
|
||||||
if [[ -s "$FILE" ]]; then
|
|
||||||
log "RESUME: fertige Datei gefunden → hochladen"
|
|
||||||
rclone move "$FILE" "$DST" --config "$CONF" --create-empty-src-dirs -v
|
|
||||||
rm -f "$PART" "$TEMP" || true
|
|
||||||
log "OK: $ID hochgeladen (Resume-Pfad)"
|
|
||||||
log "=== Done VOD $ID ==="
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
log "START: Download mit yt-dlp"
|
|
||||||
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"; }
|
[[ -s "$FILE" ]] || { [[ -s "$TEMP" ]] && mv -f "$TEMP" "$FILE"; }
|
||||||
if [[ ! -s "$FILE" ]]; then
|
if [[ ! -s "$FILE" ]]; then log "ERROR: Download fehlgeschlagen ($FILE fehlt/leer)"; exit 2; fi
|
||||||
log "ERROR: Download fehlgeschlagen ($FILE fehlt/leer)"
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
log "OK: Download abgeschlossen ($(du -h "$FILE" | awk '{print $1}'))"
|
|
||||||
|
|
||||||
log "START: Upload nach Nextcloud"
|
|
||||||
rclone move "$FILE" "$DST" --config "$CONF" --create-empty-src-dirs -v
|
rclone move "$FILE" "$DST" --config "$CONF" --create-empty-src-dirs -v
|
||||||
log "OK: Upload abgeschlossen"
|
|
||||||
|
|
||||||
rm -f "$PART" "$TEMP" || true
|
rm -f "$PART" "$TEMP" || true
|
||||||
log "CLEANUP: temporäre Dateien entfernt"
|
|
||||||
log "=== Done VOD $ID ==="
|
log "=== Done VOD $ID ==="
|
||||||
```
|
```
|
||||||
|
Rechte setzen:
|
||||||
3) Rechte setzen
|
|
||||||
```bash
|
```bash
|
||||||
chmod 755 <clipper-ordner>/bin <clipper-ordner>/bin/clipper-vod-get
|
chmod 755 <clipper-ordner>/bin/clipper-vod-get
|
||||||
chown clipper:clipper <clipper-ordner>/bin/clipper-vod-get
|
chown clipper:clipper <clipper-ordner>/bin/clipper-vod-get
|
||||||
```
|
```
|
||||||
|
> [!NOTE]
|
||||||
> **Hinweis**
|
> Pro VOD entsteht ein Logfile in `<clipper-ordner>/logs/<ID>.log`. Du kannst es live mit `tail -f <clipper-ordner>/logs/<ID>.log` verfolgen.
|
||||||
> Pro VOD entsteht ein Logfile in `<clipper-ordner>/logs/<ID>.log`. Live-Ansicht mit `tail -f <clipper-ordner>/logs/<ID>.log`
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**C. n8n‑Verkabelung (Überblick)**
|
Mit diesem Aufbau ist der **False-Pfad** fertig: Wenn die State-Datei fehlt oder leer ist, werden alle VODs verarbeitet und in Nextcloud hochgeladen. Der True-Pfad folgt im nächsten Abschnitt.
|
||||||
Ort: *n8n Weboberfläche*
|
|
||||||
|
|
||||||
**Verkabelung (Kurzüberblick):**
|
|
||||||
1) HTTP Request →
|
|
||||||
2) Split Out: →
|
|
||||||
3) Merge →
|
|
||||||
4) Split In Batches →
|
|
||||||
5) SSH Node 1 (State-Datei schreiben) →
|
|
||||||
6) SSH Node 2 (Download & Upload)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
** Node-Einstellungen (1:1 in n8n eintragen)**
|
### Schritt 8: True-Pfad – Check und ggf. Download
|
||||||
|
|
||||||
**1) HTTP Request – Get Videos**
|
|
||||||
- **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) Item Lists – Split Out**
|
> [!NOTE]
|
||||||
- **Node-Typ:** Split Out
|
> Der **False-Pfad** wurde bereits im vorherigen Abschnitt vollständig erklärt. Viele Nodes überschneiden sich mit dem True-Pfad. Damit du nicht alles doppelt anlegen musst, verweisen wir hier auf die bereits erstellten und konfigurierten Nodes.
|
||||||
- **Field to Split Out:** `data`
|
|
||||||
|
|
||||||
**3) Merge – Combine**
|
|
||||||
- **Node-Typ:** Merge
|
|
||||||
- **Mode:** Combine
|
|
||||||
- **Combine Mode:** All Possible Combinations
|
|
||||||
- **Eingang 1:** Switch (Ausgang: CREATE_AND_DOWNLOAD)
|
|
||||||
- **Eingang 2:** Item Lists: Split Out
|
|
||||||
|
|
||||||
**4) Split In Batches**
|
|
||||||
- **Node-Typ:** Split In Batches
|
|
||||||
- **Batch Size:** `1`
|
|
||||||
|
|
||||||
**5) SSH Node 1 – State-Datei schreiben**
|
|
||||||
- **Node-Typ:** SSH
|
|
||||||
- **Credentials:** *SSH Clipper* (User = `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`}}
|
|
||||||
```
|
|
||||||
|
|
||||||
**6) SSH Node 2 – Download & Upload (Skript)**
|
|
||||||
- **Node-Typ:** SSH
|
|
||||||
- **Credentials:** *SSH Clipper* (User = `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) }}"
|
|
||||||
```
|
|
||||||
- **Hinweis:** Keine Binary-Nodes hinter diesem Schritt – Upload erfolgt im Skript via rclone.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Ergebnis**
|
|
||||||
- n8n steuert den gesamten Prozess, Upload erfolgt zuverlässig im Clipper via rclone.
|
|
||||||
- Pro VOD entsteht **eine** MP4 in Nextcloud: `<gewünschter Ordner>/VODs/<ID>/`.
|
|
||||||
- Pro VOD gibt es ein eigenes Logfile unter `<clipper-ordner>/logs/<ID>.log`.
|
|
||||||
- Lokaler Speicher bleibt frei (automatisches Löschen nach Upload).
|
|
||||||
- Logs der Schritte findest du im SSH‑Node‑Output und persistent im Logfile.
|
|
||||||
|
|
||||||
8. ** True-Pfad - Check und ggf. Download
|
|
||||||
|
|
||||||
**Verkabelung (Kurzüberblick):**
|
**Verkabelung (Kurzüberblick):**
|
||||||
1) HTTP Request →
|
1) HTTP Request →
|
||||||
2) Split Out →
|
2) Split Out →
|
||||||
3) Set – vods in Array →
|
3) Set – vods in Array →
|
||||||
4) Split Arry Out →
|
4) Split Out Array →
|
||||||
5) Merge →
|
5) Merge →
|
||||||
6) Split In Batches →
|
6) Split In Batches →
|
||||||
7) SSH Node 1 (State-Datei schreiben) →
|
7) SSH – Write State →
|
||||||
8) SSH Node 2 (Download & Upload)
|
8) SSH – Download VOD
|
||||||
|
|
||||||
** Node-Einstellungen (1:1 in n8n eintragen)**
|
|
||||||
**1) HTTP Request – Get Videos**
|
---
|
||||||
- **Node-Typ:** HTTP Request
|
|
||||||
- **Methode:** GET
|
|
||||||
- **URL:**
|
### Node-Einstellungen (1:1 in n8n eintragen)
|
||||||
|
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> Die folgenden Nodes haben wir im vorherigen Schritt (False-Pfad) bereits erstellt und konfiguriert. Wenn du dem Tutorial bis hierhin gefolgt bist, musst du an diesen Nodes nichts mehr verändern:
|
||||||
|
> - **HTTP Request – Get Videos**
|
||||||
|
> - **Split Out – data**
|
||||||
|
> - **Split In Batches**
|
||||||
|
> - **SSH – Write State**
|
||||||
|
> - **SSH – Download VOD**
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
**Neu im True-Pfad:**
|
||||||
|
|
||||||
|
|
||||||
|
**3) Set – vods in Array** (Node-Name: `Set – vods in Array`)
|
||||||
|
- Node-Typ: Set
|
||||||
|
- Field: vods
|
||||||
|
- Expression:
|
||||||
|
```js
|
||||||
|
{{ (typeof $json.vods === 'string' ? $json.vods : String($json.vods))
|
||||||
|
.split(',')
|
||||||
|
.map(s => s.trim())
|
||||||
|
.filter(Boolean) }}
|
||||||
```
|
```
|
||||||
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) Item Lists – Split Out**
|
|
||||||
- **Node-Typ:** Split Out
|
|
||||||
- **Field to Split Out:** `data`
|
|
||||||
|
|
||||||
**3) Set – vods in Array**
|
**4) Split Out – vods** (Node-Name: `Split Out – vods`)
|
||||||
- **Node-Typ:** Edit Fields (set)
|
- Node-Typ: Split Out
|
||||||
- **Field:** vods
|
- Field to Split Out: vods
|
||||||
- **Type:** Arry
|
|
||||||
- **Expression** `{{ (typeof $json.vods === 'string' ? $json.vods : String($json.vods)) .split(',') .map(s => s.trim()) .filter(Boolean) }}`
|
|
||||||
|
|
||||||
**4) Item Lists – Split Out**
|
|
||||||
- **Node-Typ:** Split Out
|
|
||||||
- **Field to Split Out:** `vods`
|
|
||||||
|
|
||||||
**5) Merge – Combine**
|
**5) Merge – Combine** (Node-Name: `Merge – Combine`)
|
||||||
- **Node-Typ:** Merge
|
- Node-Typ: Merge
|
||||||
- **Mode:** Combine
|
- Mode: Combine
|
||||||
- **Combine Mode:** Matching Fields
|
- Combine Mode: Matching Fields
|
||||||
- **Fields To Match Have Different Names:** ON
|
- Fields To Match Have Different Names: ON
|
||||||
- **Field 1:** vods
|
- Field 1: vods
|
||||||
- **Field 2:** data.id
|
- Field 2: data.id
|
||||||
- **Output Type:** Keep Non-Matches
|
- Output Type: Keep Non-Matches
|
||||||
- **Output Data From:** Input 2
|
- Output Data From: Input 2
|
||||||
- **Eingang 1:** Split Out Array VODs
|
- Eingang 1: Split Out Array VODs
|
||||||
- **Eingang 2:** Split Out Twitch VODs
|
- Eingang 2: Split Out Twitch VODs
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
### Ergebnis
|
||||||
|
- Es werden nur **neue VODs** heruntergeladen und hochgeladen.
|
||||||
|
- Die State-Datei wird erweitert, ohne bestehende IDs zu überschreiben.
|
||||||
|
- Logs bleiben konsistent, Doppel-Downloads werden vermieden.
|
||||||
|
- Jeder Node ist eindeutig benannt, was die Übersicht verbessert.
|
||||||
Reference in New Issue
Block a user