Kapitel 13/Tutorial.md aktualisiert

This commit is contained in:
2025-08-30 13:09:51 +00:00
parent f280385d54
commit f4a18069dd

View File

@@ -123,6 +123,7 @@ CLIPPER_IN=/srv/clipper/watch
CLIPPER_OUT=/srv/clipper/out CLIPPER_OUT=/srv/clipper/out
CLIPPER_TMP=/srv/clipper/temp CLIPPER_TMP=/srv/clipper/temp
CLIPPER_LOG=/srv/clipper/logs/clipper.log CLIPPER_LOG=/srv/clipper/logs/clipper.log
CLIPPER_NC_REMOTE="nc:<DEIN-REMOTE>/<BASISPFAD>"
``` ```
Dateirechte setzen, damit `clipper` sie lesen darf: Dateirechte setzen, damit `clipper` sie lesen darf:
```bash ```bash
@@ -436,32 +437,79 @@ In diesem Schritt erstellen wir den eigentlichen Workflow in **n8n**. Er sorgt d
```bash ```bash
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
. /etc/clipper/clipper.env
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"
LOGDIR="<clipper-ordner>/logs" TMP="${CLIPPER_TMP}"
OUT_BASE="${CLIPPER_OUT}/${ID}"
LOGDIR="${CLIPPER_LOG}/${ID}"
CONF="/home/clipper/.config/rclone/rclone.conf" CONF="/home/clipper/.config/rclone/rclone.conf"
DST="nc:<gewünschter Ordner>/VODs/${ID}/"
DST="${CLIPPER_NC_REMOTE}/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"
mkdir -p "$TMP" "$LOGDIR" mkdir -p "$TMP" "$LOGDIR"
log() { echo "[$(date '+%F %T')] $*" | tee -a "$LOG"; } LOG="$LOGDIR/download.log"
log(){ echo "[$(date '+%F %T')] $*"; }
exec > >(tee -a "$LOG") 2>&1
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 log "DST: $DST"
if [[ -e "$LOCK" ]]; then log "LOCK: $ID wird bereits verarbeitet"; exit 0; fi
trap 'rm -f "$LOCK"' EXIT; : > "$LOCK" if rclone lsf "$DST" --config "$CONF" >/dev/null 2>&1; then
if [[ -s "$TEMP" && ! -s "$FILE" ]]; then mv -f "$TEMP" "$FILE"; fi log "SKIP: $ID bereits in Nextcloud"
if [[ -s "$FILE" ]]; then rclone move "$FILE" "$DST" --config "$CONF" --create-empty-src-dirs -v; rm -f "$PART" "$TEMP" || true; exit 0; fi exit 0
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" fi
[[ -s "$FILE" ]] || { [[ -s "$TEMP" ]] && mv -f "$TEMP" "$FILE"; }
if [[ ! -s "$FILE" ]]; then log "ERROR: Download fehlgeschlagen ($FILE fehlt/leer)"; exit 2; 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 rclone move "$FILE" "$DST" --config "$CONF" --create-empty-src-dirs -v
rm -f "$PART" "$TEMP" || true 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 ===" log "=== Done VOD $ID ==="
``` ```
Rechte setzen: Rechte setzen:
@@ -479,83 +527,83 @@ Mit diesem Aufbau ist der **False-Pfad** fertig: Wenn die State-Datei fehlt oder
--- ---
### Schritt 8: True-Pfad Check und ggf. Download ### Schritt 8: True-Pfad Check und ggf. Download
> [!NOTE] > [!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. > 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):** **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 Out Array → 4) Split Out Array →
5) Merge → 5) Merge →
6) Split In Batches → 6) Split In Batches →
7) SSH Write State → 7) SSH Write State →
8) SSH Download VOD 8) SSH Download VOD
--- ---
### Node-Einstellungen (1:1 in n8n eintragen) ### Node-Einstellungen (1:1 in n8n eintragen)
> [!IMPORTANT] > [!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: > 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** > - **HTTP Request Get Videos**
> - **Split Out data** > - **Split Out data**
> - **Split In Batches** > - **Split In Batches**
> - **SSH Write State** > - **SSH Write State**
> - **SSH Download VOD** > - **SSH Download VOD**
--- ---
**Neu im True-Pfad:** **Neu im True-Pfad:**
**3) Set vods in Array** (Node-Name: `Set vods in Array`) **3) Set vods in Array** (Node-Name: `Set vods in Array`)
- Node-Typ: Set - Node-Typ: Set
- Field: vods - Field: vods
- Expression: - Expression:
```js ```js
{{ (typeof $json.vods === 'string' ? $json.vods : String($json.vods)) {{ (typeof $json.vods === 'string' ? $json.vods : String($json.vods))
.split(',') .split(',')
.map(s => s.trim()) .map(s => s.trim())
.filter(Boolean) }} .filter(Boolean) }}
``` ```
**4) Split Out vods** (Node-Name: `Split Out vods`) **4) Split Out vods** (Node-Name: `Split Out vods`)
- Node-Typ: Split Out - Node-Typ: Split Out
- Field to Split Out: vods - Field to Split Out: vods
**5) Merge Combine** (Node-Name: `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 ### Ergebnis
- Es werden nur **neue VODs** heruntergeladen und hochgeladen. - Es werden nur **neue VODs** heruntergeladen und hochgeladen.
- Die State-Datei wird erweitert, ohne bestehende IDs zu überschreiben. - Die State-Datei wird erweitert, ohne bestehende IDs zu überschreiben.
- Logs bleiben konsistent, Doppel-Downloads werden vermieden. - Logs bleiben konsistent, Doppel-Downloads werden vermieden.
- Jeder Node ist eindeutig benannt, was die Übersicht verbessert. - Jeder Node ist eindeutig benannt, was die Übersicht verbessert.