Kapitel 13/Tutorial.md aktualisiert

This commit is contained in:
2025-09-18 17:03:50 +00:00
parent 084416db63
commit ec105f3fc5

View File

@@ -825,10 +825,22 @@ Das folgende Schaubild zeigt dir die konkrete Verkabelung
```
```bash
#!/bin/bash
#!/bin/bash
set -euo pipefail
# Cleanup bei CTRL+C, SIGTERM, etc.
cleanup() {
echo "[WARN] Analyse abgebrochen prüfe auf ffmpeg-Leichen..."
pkill -P $$ ffmpeg || true
pkill -f "ffmpeg.*$VOD_ID" || true
# FIFO säubern (nur wenn gesetzt & vorhanden)
if [ -n "${FIFO_PATH-}" ] && [ -p "$FIFO_PATH" ]; then rm -f "$FIFO_PATH"; fi
exit 1
}
trap cleanup SIGINT SIGTERM
# Umgebungsvariablen laden
if [ -f "/etc/clipper/clipper.env" ]; then
set -a
@@ -861,7 +873,7 @@ echo "[INFO] VOD gefunden: $VOD_PATH"
echo "[INFO] Extrahiere WAV aus $VOD_PATH → $TMP_AUDIO"
# Audio extrahieren mit Logging
ffmpeg -v warning -i "$VOD_PATH" -ac 1 -ar 16000 -vn "$TMP_AUDIO" 2> "$TMP_LOG_AUDIO"
nice -n 10 ffmpeg -v warning -threads 1 -i "$VOD_PATH" -ac 1 -ar 16000 -vn "$TMP_AUDIO" 2> "$TMP_LOG_AUDIO"
echo "[OK] Audio extrahiert: $TMP_AUDIO ($(du -h "$TMP_AUDIO" | cut -f1))"
echo "[INFO] Verwende Schwelle: $CLIPPER_PEAK_THRESHOLD"
@@ -931,6 +943,208 @@ with open(outfile, 'w') as f:
json.dump(candidates, f)
print(f"[DONE] {outfile} geschrieben mit {len(candidates)} Clip(s)")
EOF
echo "[INFO] Starte visuelle Analyse (alle 3 Sekunden ein Frame)..."
TMP_FRAME_DIR="$TMP_DIR/frames"
mkdir -p "$TMP_FRAME_DIR"
# Videodauer ermitteln (in Sekunden)
DURATION=$(ffprobe -v error -show_entries format=duration \
-of default=noprint_wrappers=1:nokey=1 "$VOD_PATH")
DURATION=${DURATION%.*}
INTERVAL=3
EXPECTED_FRAMES=$(printf "%.0f" "$(echo "$DURATION / $INTERVAL" | bc -l)")
echo "[INFO] VOD-Dauer: ${DURATION}s → erwarte ca. ${EXPECTED_FRAMES} Bilder bei fps=1/${INTERVAL}"
# ffmpeg im Hintergrund
START_TIME=$(date +%s)
nice -n 10 ffmpeg -v warning -i "$VOD_PATH" \
-vf "fps=1/${INTERVAL},scale=160:90" \
"$TMP_FRAME_DIR/frame_%06d.bmp" &
FFMPEG_PID=$!
EXTRACTED_FRAMES=0
LAST_LOGGED_FRAMES=0
while kill -0 $FFMPEG_PID 2>/dev/null; do
EXTRACTED_FRAMES=$(find "$TMP_FRAME_DIR" -name 'frame_*.bmp' | wc -l)
if (( EXTRACTED_FRAMES > LAST_LOGGED_FRAMES )); then
NOW=$(date +%s)
ELAPSED=$((NOW - START_TIME))
ELAPSED_FMT=$(printf "%02d:%02d:%02d" $((ELAPSED/3600)) $((ELAPSED%3600/60)) $((ELAPSED%60)))
if (( EXTRACTED_FRAMES > 0 )); then
ESTIMATED_TOTAL=$((ELAPSED * EXPECTED_FRAMES / EXTRACTED_FRAMES))
ETA_SECONDS=$((ESTIMATED_TOTAL - ELAPSED))
ETA_FMT=$(printf "%02d:%02d:%02d" $((ETA_SECONDS/3600)) $((ETA_SECONDS%3600/60)) $((ETA_SECONDS%60)))
else
ETA_FMT="??:??:??"
fi
PERCENT=$(awk "BEGIN { printf \"%.2f\", $EXTRACTED_FRAMES * 100 / $EXPECTED_FRAMES }")
echo "[INFO] Lade Frame $EXTRACTED_FRAMES / $EXPECTED_FRAMES (${PERCENT}%) | ETA: $ETA_FMT | Elapsed: $ELAPSED_FMT"
LAST_LOGGED_FRAMES=$EXTRACTED_FRAMES
fi
sleep 1
done
# Final-Log
EXTRACTED_FRAMES=$(find "$TMP_FRAME_DIR" -name 'frame_*.bmp' | wc -l)
END_TIME=$(date +%s)
TOTAL_TIME=$((END_TIME - START_TIME))
TOTAL_FMT=$(printf "%02d:%02d:%02d" $((TOTAL_TIME/3600)) $((TOTAL_TIME%3600/60)) $((TOTAL_TIME%60)))
echo "[INFO] Frame-Export abgeschlossen mit $EXTRACTED_FRAMES Bildern. Dauer: $TOTAL_FMT"
echo "[INFO] Starte visuelle Analyse basierend auf Bewegung zwischen Frames..."
TMP_JSON_VISUAL="$TMP_DIR/candidates.visual.json"
/srv/clipper/.venv/bin/python3 <<EOF
import os
import json
import cv2
import numpy as np
frame_dir = "$TMP_FRAME_DIR"
output_json = "$TMP_JSON_VISUAL"
frame_files = sorted([
os.path.join(frame_dir, f) for f in os.listdir(frame_dir)
if f.endswith(".bmp")
])
movement_threshold = 12000
min_duration = 5
candidates = []
prev_frame = None
active_start = None
for idx, frame_path in enumerate(frame_files):
frame = cv2.imread(frame_path, cv2.IMREAD_GRAYSCALE)
if frame is None:
continue
if prev_frame is not None:
diff = cv2.absdiff(prev_frame, frame)
score = int(np.sum(diff))
print(f"[DEBUG] Frame {idx}: Bewegungsscore = {score}")
t = idx * 3
if score > movement_threshold:
if active_start is None:
active_start = t
else:
if active_start is not None and (t - active_start) >= min_duration:
# Pre/Post-Puffer hinzufügen
start_time = max(0, active_start - 2)
end_time = min(t + 2, len(frame_files) * 3)
candidates.append({
"start": round(start_time, 2),
"end": round(end_time, 2)
})
active_start = None
prev_frame = frame
# Letzter offener Clip am Ende
if active_start is not None:
t = len(frame_files) * 3
if t - active_start >= min_duration:
start_time = max(0, active_start - 2)
end_time = min(t + 2, len(frame_files) * 3)
candidates.append({
"start": round(start_time, 2),
"end": round(end_time, 2)
})
with open(output_json, "w") as f:
json.dump(candidates, f)
print(f"[DONE] Bewegungserkennung abgeschlossen → {len(candidates)} Clip(s) geschrieben in {output_json}")
EOF
echo "[INFO] Vergleiche Audio- und visuelle Kandidaten..."
TMP_JSON_FINAL="$TMP_DIR/candidates.final.json"
CLIPPER_MATCH_TOLERANCE="${CLIPPER_MATCH_TOLERANCE:-4.0}"
/srv/clipper/.venv/bin/python3 <<EOF
import json
import os
audio_file = "$TMP_JSON"
visual_file = "$TMP_JSON_VISUAL"
output_file = "$TMP_JSON_FINAL"
tolerance = float("${CLIPPER_MATCH_TOLERANCE}")
# Overlap mit Toleranz
def overlap(a, b):
return not (a["end"] + tolerance < b["start"] or b["end"] + tolerance < a["start"])
try:
with open(audio_file) as f:
audio = json.load(f)
except:
print("[WARN] Audio-Kandidaten fehlen oder leer.")
audio = []
try:
with open(visual_file) as f:
visual = json.load(f)
except:
print("[WARN] Visuelle Kandidaten fehlen oder leer.")
visual = []
combined = []
only_audio = []
only_video = visual.copy()
matched_visual_indices = set()
for a in audio:
matched = False
for idx, v in enumerate(visual):
if overlap(a, v):
combined.append({
"start": round(min(a["start"], v["start"]), 2),
"end": round(max(a["end"], v["end"]), 2)
})
matched_visual_indices.add(idx)
matched = True
break
if not matched:
only_audio.append(a)
only_video = [
v for idx, v in enumerate(visual)
if idx not in matched_visual_indices
]
result = {
"only_audio": only_audio,
"only_video": only_video,
"combined": combined
}
with open(output_file, "w") as f:
json.dump(result, f)
print(f"[DONE] Kandidaten klassifiziert → combined={len(combined)}, only_audio={len(only_audio)}, only_video={len(only_video)}")
EOF
echo "[INFO] Ersetze ursprüngliches JSON mit finaler Version..."
mv "$TMP_JSON_FINAL" "$TMP_JSON"
```
1. **SSH Node Analyze VOD** (Node-Name: Analyze VOD)
- Node-Typ: SSH