# 🛠️ 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: ``` CLIPPER_INBOX=/srv/clipper/inbox CLIPPER_IN=/srv/clipper/watch CLIPPER_OUT=/srv/clipper/out CLIPPER_TMP=/srv/clipper/temp CLIPPER_LOG=/srv/clipper/logs/clipper.log CLIPPER_NC_REMOTE="nc:/" ``` 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)
Optional: SSH Known-Hosts zurücksetzen (falls Clipper schon einmal erstellt wurde) 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 "" ``` > [!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-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@ # << unbedingt ausführen ssh -i ~/.ssh/id_n8n_clipper clipper@ "echo OK" ``` Ersetze `` 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. --- ### Zugangsdaten für Nextcloud-WebDAV erstellen (Weboberfläche) > **Ort in Nextcloud**: Avatar (oben rechts) → **Persönliche Einstellungen** → **Sicherheit** → **ganz nach unten** zum Bereich **App-Passwörter**. **Anleitung:** 1. Melde dich in der **Nextcloud-Weboberfläche** an. 2. Gehe zu **Persönliche Einstellungen** → **Sicherheit**. 3. **Ganz nach unten scrollen** bis zum Abschnitt **App-Passwörter**. 4. Trage bei **App-Name** z. B. `n8n Clipper` ein und klicke auf **Neues App-Passwort erstellen**. 5. Kopiere das **einmalig angezeigte App-Passwort** sofort und notiere zusätzlich deinen **Benutzernamen**. **WebDAV-URL für n8n/Uploads:** ``` https:///remote.php/dav/files// ``` --- ### 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= ``` * Auth: OAuth2 Credential * Header: `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 – 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=&type=archive ``` - Authentifizierung: OAuth2 (Credential „Twitch API“) - Header: `Client-Id: ` 3. **SSH Credentials in n8n anlegen** - Damit n8n mit dem Clipper kommunizieren kann, brauchst du SSH-Zugangsdaten. - Trage in n8n ein: * Name: `SSH Clipper` * Host: `` (z. B. `10.0.0.42`) * Port: `22` * Username: `clipper` * Private Key: Inhalt von `~/.ssh/id_n8n_clipper` * Working Directory: `/srv/clipper` 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 Node – Ergebnisse übernehmen** (Node-Name: `Set – State Ergebnis`) - Ziel: die Ausgabe aus dem vorherigen Schritt in Felder umwandeln, die n8n versteht. - Expressions: * file_exists → `{{ JSON.parse($json.stdout).file_exists }}` * non_empty → `{{ JSON.parse($json.stdout).non_empty }}` * vods → `{{ JSON.parse($json.stdout).vods }}` > [!IMPORTANT] > Hier nur die Expressions eintragen, keine Typ-Konvertierungen vornehmen. 6. **If-Node – Entscheidung** (Node-Name: `If – State Prüfung`) - Dieser Node entscheidet, ob neue VODs heruntergeladen werden müssen. - Bedingungen: ``` 1. Bedingung: {{ $json.file_exists }} is true (Boolean) 2. Bedingung: {{ $json.non_empty }} is true (Boolean) Operator: AND ``` - Ergebnis: zwei mögliche Pfade → False = neu anlegen, True = prüfen auf neue VODs. --- 7. **False-Pfad – VODs herunterladen und hochladen** > [!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 apt update && apt install -y rclone su - clipper rclone config create nc webdav \ url=https://DEINE_DOMAIN/remote.php/dav/files/DEIN_BENUTZERNAME/ \ vendor=nextcloud \ user=DEIN_BENUTZERNAME \ pass=$(rclone obscure 'DEIN_APP_PASSWORT') rclone ls nc: --config /home/clipper/.config/rclone/rclone.conf ``` > [!NOTE] > 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 ```bash nano /bin/clipper-vod-get ``` Inhalt: ```bash #!/usr/bin/env bash set -euo pipefail . /etc/clipper/clipper.env ID="${1:?need VOD id}" URL="${2:-https://www.twitch.tv/videos/${ID}}" TMP="${CLIPPER_TMP}" OUT_BASE="${CLIPPER_OUT}/${ID}" LOGDIR="${CLIPPER_LOG}/${ID}" CONF="/home/clipper/.config/rclone/rclone.conf" DST="${CLIPPER_NC_REMOTE}/VODs/${ID}/" OUT="$TMP/${ID}.%(ext)s" FILE="$TMP/${ID}.mp4" TEMP="$TMP/${ID}.temp.mp4" PART="$TMP/${ID}.mp4.part" LOCK="$TMP/${ID}.lock" mkdir -p "$TMP" "$LOGDIR" LOG="$LOGDIR/download.log" log(){ echo "[$(date '+%F %T')] $*"; } exec > >(tee -a "$LOG") 2>&1 log "=== Start VOD $ID ===" log "URL: $URL" log "DST: $DST" if rclone lsf "$DST" --config "$CONF" >/dev/null 2>&1; then log "SKIP: $ID bereits in Nextcloud" exit 0 fi if [[ -e "$LOCK" ]]; then log "LOCK: $ID wird bereits verarbeitet" exit 0 fi trap 'rm -f "$LOCK"' EXIT : > "$LOCK" # Resume if [[ -s "$TEMP" && ! -s "$FILE" ]]; then log "RESUME: $TEMP -> $FILE" mv -f "$TEMP" "$FILE" fi if [[ -s "$FILE" ]]; then log "MOVE (resume): $FILE → $DST" rclone move "$FILE" "$DST" --config "$CONF" --create-empty-src-dirs -v rm -f "$PART" "$TEMP" || true log "CLEANUP: $TMP" rm -rf "${TMP:?}/"* log "=== Done VOD $ID (resume path) ===" exit 0 fi # Download + Remux 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 2 fi log "MOVE: $FILE → $DST" rclone move "$FILE" "$DST" --config "$CONF" --create-empty-src-dirs -v rm -f "$PART" "$TEMP" || true log "CLEANUP: $TMP" rm -rf "${TMP:?}/"* log "=== Done VOD $ID ===" ``` Rechte setzen: ```bash chmod 755 /bin/clipper-vod-get chown clipper:clipper /bin/clipper-vod-get ``` > [!NOTE] > Pro VOD entsteht ein Logfile in `/logs/.log`. Du kannst es live mit `tail -f /logs/.log` verfolgen. --- 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. --- ### Schritt 8: True-Pfad – Check und ggf. Download > [!NOTE] > 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. --- **Verkabelung (Kurzüberblick):** 1) HTTP Request → 2) Split Out → 3) Set – vods in Array → 4) Split Out Array → 5) Merge → 6) Split In Batches → 7) SSH – Write State → 8) SSH – Download VOD --- ### 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) }} ``` **4) Split Out – vods** (Node-Name: `Split Out – vods`) - Node-Typ: Split Out - Field to Split Out: vods **5) Merge – Combine** (Node-Name: `Merge – Combine`) - Node-Typ: Merge - Mode: Combine - Combine Mode: Matching Fields - Fields To Match Have Different Names: ON - Field 1: vods - Field 2: data.id - Output Type: Keep Non-Matches - Output Data From: Input 2 - Eingang 1: Split Out Array 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. --- # 🧪 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.