Kapitel 13/Tutorial.md aktualisiert
This commit is contained in:
@@ -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.
|
||||||
Reference in New Issue
Block a user