Files
Homelab--Bratonein-Kontroll…/Kapitel 13/Tutorial.md

36 KiB
Raw Blame History

🛠️ 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, 12 GB RAM, 10 GB Speicher reichen aus
  • Grundwissen: SSH-Verbindung, Nano-Editor, Basiskenntnisse in n8n

Vorbereitung

Wir beginnen mit einem frischen Debian12LXC in Proxmox, benennen ihn clipper und vergeben die im Abschnitt oben genannten Ressourcen. Danach bringen wir das System auf Stand und installieren die Grundwerkzeuge:

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:

timedatectl status

Wenn hier UTC steht und du lieber „Europe/Berlin“ nutzen willst:

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:

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 ClipperLXC einrichten (Benutzer, Verzeichnisse, Pakete, Skripte)

In diesem Abschnitt richten wir den ClipperContainer so ein, dass SSHSchlüssel, Downloads und n8nAufrufe ohne Berechtigungsfehler funktionieren. Wir arbeiten jetzt im Terminal deines ClipperLXC als root.

Entscheidung & Begründung Benutzer mit Home & Bash
Der Benutzer clipper bekommt ein Homeverzeichnis (/home/clipper) und eine LoginShell. So können SSHSchlüssel sauber in ~clipper/.ssh landen und n8n später per SSH Befehle ausführen. Varianten ohne Home (SystemUser) führen bei ssh-copy-id zu Fehlern.

2.1 Benutzer und Verzeichnisse anlegen (Terminal, als root)

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:

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):

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)

apt update && apt install -y yt-dlp jq python3 python3-venv curl unzip inotify-tools sudo
  • ffmpeg: Analyse & Schnitt
  • yt-dlp: TwitchVOD/ClipDownloads (HLS)
  • jq: JSONHandling
  • python3/venv: spätere AnalyseTools
  • inotify-tools: DateisystemEvents (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:

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:

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
DROP_BASE="/mnt/hdd/incoming"

CLIPPER_IN=/srv/clipper/watch
CLIPPER_OUT=/srv/clipper/out
CLIPPER_TMP=/srv/clipper/temp
CLIPPER_LOG=/srv/clipper/logs

Dateirechte setzen, damit clipper sie lesen darf:

chown root:clipper /etc/clipper/clipper.env
chmod 640 /etc/clipper/clipper.env

2.4 PythonUmgebung vorbereiten (Wechsel zu Benutzer clipper)

Wechsle jetzt zum Benutzer clipper:

su - clipper

Erzeuge und fülle eine virtuelle Umgebung für die spätere Analyse:

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)

AnalyseStub prüft Eingaben, schreibt Logs, erzeugt leere Kandidatenliste:

nano /srv/clipper/bin/clipper-analyze

Inhalt:

#!/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"

SchneidStub protokolliert Schnittaufrufe, echte Logik folgt in Abschnitt 5:

nano /srv/clipper/bin/clipper-cut

Inhalt:

#!/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/$JOBID/cut.log"
exit 0

Rechte setzen und Eigentümer korrigieren:

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:

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 SSHSchlü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)

SSH-Schlüssel für Clipper hinterlegen (zwingend):

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.

    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

    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.

    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

    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

    Connected to 192.168.51.2.
    sftp>
    

    anzeigen. Mit quitbeendst 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.

    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.

    useradd -m -s /bin/bash nc_runner
    passwd -l nc_runner
    

    Damit ist der Benutzer erstellt und bereit für den nächsten Schritt.
    Dort erzeugen wir für den neuen Benutzer ein Schlüsselpaar, das für die Verbindung genutzt wird.

    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.

    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.

    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:

    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:

    visudo
    

    Ganz am Ende fügen wir folgende Zeile ein:

    nc_runner ALL=(www-data) NOPASSWD: /usr/bin/php /srv/nextcloud/app/nextcloud/occ *
    nc_runner ALL=(root)     NOPASSWD: /usr/local/bin/nc_finalize_vod.sh
    

    Hierdurch erlauben wir dem Benutzer nc_runner, genau diese Befehle als www-data auszuführen und nichts anderes.

    Zum Abschluss testen wir, ob alles funktioniert. Wechsle dazu wieder auf deinen Clipper LXC und rufe folgenden Befehl auf:

    ssh -i ~/.ssh/nc_runner_ed25519 nc_runner@<NEXTCLOUD-IP> "sudo -u www-data php /srv/nextcloud/app/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 ConsoleApplicationsRegister 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):
    {{`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:
    {"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:
{{ (typeof $json.vods === 'string' ? $json.vods : String($json.vods))
.split(',')
.map(s => s.trim())
.filter(Boolean) }}
  1. Split Out vods (Node-Name: Split Out vods)
  • Node-Typ: Split Out
  • Field to Split Out: vods
  1. Vorbereitungen für VOD Download und Speicherung

    **8.1 Vorbereitung (einmalig) SFTP-Verbindung testen Ort: Clipper-LXC Shell

    EInmalig auf dem Nextcloud LXC:

    mkdir -p /mnt/hdd/incoming
    chown sftp_uploader:sftp_uploader /mnt/hdd/incoming
    chmod 700 /mnt/hdd/incoming
    

    Wir prüfen, ob Clipper mit dem Key des sftp_uploader auf den Nextcloud-Host kommt und die Drop-Zone erreichbar ist.

    mkdir -p /home/sftp_uploader/incoming
    echo "/mnt/hdd/incoming /home/sftp_uploader/incoming none bind 0 0" >> /etc/fstab
    mount /home/sftp_uploader/incoming
    systemctl daemon-reload
    

    Note

    Nun sieht der SFTP-Prozess weiterhin nur /home/sftp_uploader/incoming, speichert aber tatsächlich auf der HDD.
    Vorteil: Keine Änderung an Pfaden nötig alle Uploads landen automatisch auf dem großen Speicher.

    su - clipper
    
    sftp -i ~/.ssh/nc_sftp_ed25519 -oBatchMode=yes -oStrictHostKeyChecking=accept-new -P 22 sftp_uploader@<IP_des_Nextcloud_LXC> 
    mkdir /mnt/hdd/incoming/_test
    ls /mnt/hdd/incoming
    rmdir /mnt/hdd/incoming/_test
    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.

    nano <clipper-ordner>/bin/clipper-vod-get
    

    Nun befüllst du sie mit:

      #!/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:

    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.

    Die Datei /etc/nc_uploader.conf wird lokal auf dem Nextcloud-LXC abgelegt. Sie enthält alle Pfade und Parameter, die das Skript nc_finalize_vod.sh benötigt, um Dateien aus der SFTP-Dropzone korrekt zu verschieben und in Nextcloud zu integrieren.

    nano /etc/nc_uploader.conf
    

    Fülle sie mit:

    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="/mnt/hdd/nextcloud_data"   # Basis-Datenverzeichnis deiner Nextcloud-Instanz
    DROP_BASE="/home/sftp_uploader/incoming"  # SFTP-Drop-Zone von sftp_uploader
    PHP="/usr/bin/php"
    OCC="/srv/nextcloud/app/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.

    nano /usr/local/bin/nc_finalize_vod.sh
    

    Inhalt:

    #!/usr/bin/env bash
    # /usr/local/bin/nc_finalize_vod.sh
    
    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}"
    
    # ---- Eingaben & Pfade
    DROP_BASE="${DROP_BASE:?DROP_BASE fehlt in Config}"
    NC_DATA="${NC_DATA:?NC_DATA fehlt in Config}"
    NC_USER="${NC_USER:?NC_USER fehlt in Config}"
    NC_TARGET_SUBPATH="${NC_TARGET_SUBPATH:?NC_TARGET_SUBPATH fehlt in Config}"
    PHP="${PHP:-/usr/bin/php}"
    OCC="${OCC:-/var/www/nextcloud/occ}"
    
    SRC_DIR="${DROP_BASE}/${VOD_ID}"
    DST_BASE="${NC_DATA}/${NC_USER}/files"
    DST_DIR="${DST_BASE}/${NC_TARGET_SUBPATH}/${VOD_ID}"
    SCAN_PATH="${NC_USER}/files/${NC_TARGET_SUBPATH}/${VOD_ID}"
    
    # ---- Vorbedingungen prüfen (wir legen KEINE Benutzerstruktur an!)
    [[ -d "$NC_DATA" ]]     || { echo "NC_DATA nicht gefunden: $NC_DATA" >&2; exit 2; }
    [[ -d "$DST_BASE" ]]    || { echo "Benutzerdateien fehlen: $DST_BASE (stimmt NC_USER/NC_DATA?)" >&2; exit 3; }
    [[ -d "$SRC_DIR" ]]     || { echo "Drop-Ordner fehlt: $SRC_DIR" >&2; exit 4; }
    
    # ---- Ziel-Unterordner anlegen (falls nicht vorhanden)
    mkdir -p "$DST_DIR"
    chown -R www-data:www-data "$DST_BASE/$NC_TARGET_SUBPATH"
    
    # ---- Dateien/Ordner verschieben
    shopt -s nullglob dotglob
    had_files=false
    for f in "${SRC_DIR}/"*; do
    had_files=true
    mv -f "$f" "$DST_DIR/"
    done
    # Leeren Drop-Ordner entfernen (optional)
    rmdir "$SRC_DIR" 2>/dev/null || true
    shopt -u nullglob dotglob
    
    if [[ "$had_files" = false ]]; then
    echo "Keine Dateien in Drop-Ordner: ${SRC_DIR}" >&2
    exit 5
    fi
    
    # ---- Rechte setzen wie von Nextcloud erwartet
    chown -R www-data:www-data "$DST_DIR"
    
    # ---- Index nur für diesen Pfad aktualisieren
    sudo -n -u www-data "$PHP" "$OCC" files:scan --path="$SCAN_PATH" --quiet
    
    # ---- Übrig gebliebene .lock-Dateien entfernen (z.B. durch Abbrüche bei Upload)
    find "$DROP_BASE" -type f -name "*.lock" -delete 2>/dev/null || true
    
    # ---- Erfolgsmeldung
    echo "OK: $(printf '%s\n' "$DST_DIR")"
    
    
    

    Wie zuvor auch, müssen wir die Rechte korrekt setzen, damit alles reibungslos funktioniert. Gebe dazu in der Konsole

    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.

  2. 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.
  3. 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.
  1. 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:
   set -euo pipefail; STATE="/srv/clipper/state/vod_seen.list"; mkdir -p "$(dirname "$STATE")"; printf '%s\n' "{{$json.data.id}}" >> "$STATE"
  1. 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:
   <clipper-ordner>/bin/clipper-vod-get "{{$('Merge').item.json.data.id}}" "{{ $json.url || ('https://www.twitch.tv/videos/' + $('Merge').item.json.data.id) }}"
  1. 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:
{{`ssh -i /root/.ssh/nc_runner_ed25519 nc_runner@<NEXTCLOUD-IP> "sudo -n /usr/local/bin/nc_finalize_vod.sh ${$('Merge').item.json.data.id}"`}}



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

                        --- **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 KIAnalyse. 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 AnalyseSkript anlegen

  **Ort:** Terminal im **ClipperLXC** → als Benutzer **`clipper`**


  Öffne die Datei und füge den Inhalt ein:


  ```bash
  nano /srv/clipper/bin/vod-analyze # (dieses Skript wird das VOD analysieren und candidates.json erzeugen)
  ```


  Inhalt:
  ```bash
  #!/usr/bin/env bash
  set -euo pipefail
  . /etc/clipper/clipper.env


  ID="${1:?need VOD id}"


  VOD_IN_MP4="/srv/clipper/temp/${ID}.mp4" # temporäre Datei (vom VOD aus NC heruntergeladen)
  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 =="


  # cleanup temp VOD
  echo "== Cleanup: remove temp file $VOD_IN_MP4 =="
  rm -f "$VOD_IN_MP4"
  ```


  ---


  ## Schritt 4.2  n8n: Analyse starten
  **Ort:** n8nWeboberflä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 LogOrdner erstellt.
  • Die Analyse erzeugt pro VOD eine candidates.json mit Zeitfenstern.
  • Abschnitt 5 nutzt diese JSON für den Schnitt.