diff --git a/Kapitel 13/Tutorial.md b/Kapitel 13/Tutorial.md index 0c9a2a7..522190a 100644 --- a/Kapitel 13/Tutorial.md +++ b/Kapitel 13/Tutorial.md @@ -402,73 +402,134 @@ Referenz: [Get Users](https://dev.twitch.tv/docs/api/reference#get-users) Jetzt bauen wir den eigentlichen Workflow, der regelmäßig neue VODs prüft, herunterlädt und ablegt. -1. **Cron‑Trigger anlegen:** - - Ziehe einen **Cron Node** auf die Arbeitsfläche. - - Stelle ihn auf „Every 10 minutes“. - Damit prüft n8n alle 10 Minuten auf neue VODs. +1. **Cron-Trigger anlegen:** + + * Ziehe einen **Cron Node** auf die Arbeitsfläche. + * Stelle ihn auf „Every 10 minutes“. + Damit prüft n8n alle 10 Minuten auf neue VODs. + +2. **VOD-Daten abrufen:** + + * Ziehe einen weiteren **HTTP Request Node** an den Cron-Node. + * URL: -2. **VOD‑Daten abrufen:** - - Ziehe einen weiteren **HTTP Request Node** an den Cron‑Node. - - URL: ``` https://api.twitch.tv/helix/videos?user_id=&type=archive&first=1 ``` - - Auth: OAuth2 Credential - - Header: `Client-Id: ` - - Ergebnis: In `data[0]` stehen `id`, `title`, `url`, `created_at`, `published_at`. + * Auth: OAuth2 Credential + * Header: `Client-Id: ` + * Ergebnis: In `data[0]` stehen `id`, `title`, `url`, `created_at`, `published_at`. Referenz: [Get Videos](https://dev.twitch.tv/docs/api/reference#get-videos) -3. **Clips abrufen (optional):** - - Ziehe einen weiteren **HTTP Request Node** an. - - URL: +3. **IF „Neu?“ Node hinzufügen:** + + * Bedingung (Expression): + ``` - https://api.twitch.tv/helix/clips?broadcaster_id=&started_at=&ended_at= + {{$json.data[0].id !== $workflow.staticData.global.last_vod_id}} ``` - - Ergebnis: Liste mit Clips, die im Zeitraum des VOD erstellt wurden. - Referenz: [Get Clips](https://dev.twitch.tv/docs/api/reference#get-clips) + * true → weiter; false → Ende + + > **Entscheidung & Begründung – State merken** + > Nur wenn die ID sich ändert, ist ein neues VOD da. So entstehen keine leeren Ordner und keine unnötigen API- oder SSH-Aufrufe. + +4. **VOD herunterladen (SSH Execute Command im Clipper-LXC):** + + * Node: **SSH Execute Command** (Credential: *Clipper*) + + * Command: + + ```bash + JOBID="{{ $json.data[0].id }}" + URL="{{ $json.data[0].url }}" + DIR="/srv/clipper/inbox/$JOBID" + + mkdir -p "$DIR" + + yt-dlp \ + --download-archive "/srv/clipper/logs/yt-dlp.archive" \ + --no-part --restrict-filenames \ + --abort-on-unavailable-fragment \ + --retries 10 --fragment-retries 10 --retry-sleep 5 \ + -o "$DIR/%(title)s-%(id)s.%(ext)s" \ + "$URL" + ``` + + > **Entscheidung & Begründung – yt-dlp Archive** + > `--download-archive` verhindert doppelte Downloads. Retries sichern HLS-Streams ab. Alles passiert auf Clipper, aber **gesteuert durch n8n**. + +5. **Clips abrufen (optional):** + + * Node: **HTTP Request** + * URL: + + ``` + https://api.twitch.tv/helix/clips?broadcaster_id=&started_at={{$json.data[0].created_at}}&ended_at={{$json.data[0].published_at}} + ``` + * Danach: **Split in Batches** → je Clip-URL an **SSH Execute Command** + + Command (SSH): -4. **VOD herunterladen (Terminal im Clipper‑LXC):** - Jetzt wechselst du ins **Terminal des Clipper‑LXC**. Stelle sicher, dass `yt-dlp` installiert ist (`apt install -y yt-dlp`). n8n ruft per SSH folgenden Befehl auf: ```bash - mkdir -p "/srv/clipper/inbox/{{ $json.data[0].id }}" - yt-dlp -o "/srv/clipper/inbox/{{ $json.data[0].id }}/%(title)s.%(ext)s" {{ $json.data[0].url }} - ``` - Damit entsteht ein Unterordner `/srv/clipper/inbox//` mit dem Video. + CID="{{ $json.id }}" + CURL="{{ $json.url }}" + DIR="/srv/clipper/inbox/$CID" - > **Entscheidung & Begründung – Warum lädt *Clipper* (und nicht *n8n*)?** - > VODs kommen als **HLS‑Streams** (m3u8, segmentiert, signiert). Ein robuster Download braucht **yt‑dlp/ffmpeg** mit Retry/Resume und I/O‑Puffer. Das ist CPU/IO‑lastig und gehört auf die **Werkbank** (Clipper), nicht in die **Steuerzentrale** (n8n). So bleibt n8n stabil und reagiert weiter auf Webhooks. + mkdir -p "$DIR" -5. **Clips herunterladen (Terminal im Clipper‑LXC, optional):** - Für jeden Clip‑URL ruft n8n per SSH `yt-dlp` auf: - ```bash - yt-dlp -o "/srv/clipper/inbox//%(title)s.%(ext)s" https://clips.twitch.tv/ + yt-dlp \ + --download-archive "/srv/clipper/logs/yt-dlp.archive" \ + --no-part --restrict-filenames \ + --retries 10 --fragment-retries 10 --retry-sleep 5 \ + -o "$DIR/%(title)s-%(id)s.%(ext)s" \ + "$CURL" ``` -6. **Upload nach Nextcloud (n8n‑Weboberfläche):** - - Ziehe einen **HTTP Request Node** auf die Arbeitsfläche. - - Methode: **PUT** - - Auth: **Basic Auth** (Benutzername + App‑Passwort aus Schritt 1) - - URL: +6. **Upload nach Nextcloud (n8n‑Weboberfläche):** + + * Node: **HTTP Request** + + * Methode: **PUT** + + * Auth: **Basic Auth** (Nextcloud App-Passwort) + + * URL: + ``` https:///remote.php/dav/files//Clips/{{ $json.data[0].id }}/ ``` - - Ergebnis: In Nextcloud liegt danach ein Ordner `Clips//…`. - > **Entscheidung & Begründung – Warum Nextcloud?** + * Ergebnis: In Nextcloud liegt danach ein Ordner `Clips//…`. + + > **Entscheidung & Begründung – Warum Nextcloud?** > Zentrales, versionierbares Speicherziel mit Freigaben/Quotas/Backups. Clipper und andere Systeme können darauf zugreifen, ohne erneut von Twitch laden zu müssen. -7. **Analyse starten (Terminal im Clipper‑LXC):** - Zum Abschluss ruft n8n per SSH das Skript `clipper-analyze` auf: - ```bash - /srv/clipper/bin/clipper-analyze \ - "/srv/clipper/inbox/{{ $json.data[0].id }}/{{ $json.data[0].title }}.mp4" \ - "vod-{{ $json.data[0].id }}" - ``` - Das Skript erzeugt im Clipper‑Temp‑Ordner eine `candidates.json`. Mit dieser arbeiten wir in Abschnitt 4 weiter. +7. **Analyse starten (SSH Execute Command):** + + * Command: + + ```bash + VIDDIR="/srv/clipper/inbox/{{ $json.data[0].id }}" + MAINFILE="$(find "$VIDDIR" -maxdepth 1 -type f -name '*.mp4' -o -name '*.mkv' | head -n1)" + /srv/clipper/bin/clipper-analyze "$MAINFILE" "vod-{{ $json.data[0].id }}" + ``` + + > **Entscheidung & Begründung – Arbeitsteilung** + > n8n stößt nur an, Clipper rechnet. So bleibt n8n leicht und ausfallsicher. + +8. **State aktualisieren (n8n‑Weboberfläche):** + + * Node: **Function** oder **Set** + + ```js + $workflow.staticData.global.last_vod_id = $json.data[0].id; + return items; + ``` + + Damit merkt sich der Workflow, welches VOD zuletzt verarbeitet wurde. ---- ### Alternative: Twitch‑Community‑Node (n8n‑Weboberfläche)