Video-Thumbnails mit cURL und FFmpeg-Pipes streamen
Das Extrahieren von Thumbnails aus entfernten Videos kann das Herunterladen großer Dateien erfordern. Bei Dateien, deren Metadaten und angeforderte Frames nahe am Anfang liegen, kann ein Byte-Range-Request diesen Download verringern. Diese Anleitung zeigt die Pipe-Technik und ein Bash-Hilfsskript, das auf einen vollständigen, frei navigierbaren Download zurückgreift, wenn ein Präfix nicht ausreicht. Byte-Ranges allein können kein brauchbares Thumbnail garantieren.
HTTP-Range-Requests mit cURL verstehen
Mit einem HTTP-Range-Request kann ein Client einen Byte-Ausschnitt einer Ressource abrufen statt des
vollständigen Objekts. Server können Unterstützung über Accept-Ranges: bytes signalisieren,
entscheidend ist jedoch die Antwort auf ein tatsächliches Range-GET.
Stellen Sie zunächst sicher, dass der Ursprungsserver Ranges unterstützt:
curl -fsSL --range 0-0 --max-filesize 1 -D - -o /dev/null https://example.com/video.mp4
Achten Sie auf eine abschließende Antwort mit 206 Partial Content und ein passendes Content-Range. Die
Größenbeschränkung verhindert, dass ein ignorierter Range während dieser Prüfung eine große Datei
herunterlädt. Um das erste 1 MiB abzurufen, verwenden Sie das Flag --range (-r) von cURL,
statt den Header manuell zu setzen:
set -euo pipefail
mkdir video-prefix
curl -fsSL -r 0-1048575 --max-filesize 1048576 https://example.com/video.mp4 -o video-prefix/head.mp4
Range-Endpunkte sind inklusiv. Ein Server kann den Range ignorieren und mit 200 OK samt vollständigem
Body antworten; cURL macht daraus nicht automatisch einen Teil-Download.
Partielle Videodaten an FFmpeg pipen
FFmpeg kann von der Standardeingabe (pipe:0) lesen und erkennt den Container in der Regel automatisch.
Verwenden Sie -f nur, wenn Sie das Format kennen. MP4-Eingaben müssen sequenzielles Lesen
unterstützen, typischerweise mit den Metadaten in moov am Anfang („faststart“); eine Pipe kann nach
dem Lesen von Metadaten am Ende nicht zu den Mediendaten zurückspringen. Die folgenden
Pipe-Beispiele setzen dieses Layout und genügend Bytes voraus, um den angeforderten Frame zu
decodieren. Führen Sie jedes Beispiel mit Bash in einem neuen Ausgabeverzeichnis aus:
set -euo pipefail
mkdir first-thumbnail
curl -fsSL https://example.com/video.mp4 | \
ffmpeg -nostdin -n -hide_banner -loglevel error \
-f mp4 -i pipe:0 \
-ss 00:00:10 -frames:v 1 -update 1 -f image2 first-thumbnail/thumbnail.jpg
Damit wird bis zehn Sekunden vorwärts decodiert und ein JPEG geschrieben, ohne das Eingabevideo zu
speichern. FFmpeg kann die Pipe schließen, sobald der Frame vorliegt, woraufhin cURL einen
Schreibfehler (23) meldet. Mit pipefail schlägt dadurch die Pipeline fehl, selbst wenn ein Bild
erzeugt wurde. Das Automatisierungsskript weiter unten lädt in eine temporäre Datei herunter, um
diese Mehrdeutigkeit zu vermeiden und Sprünge in der Datei sowie Retries zu ermöglichen.
Einen passenden Byte-Range berechnen
Wie viele Bytes Sie benötigen, hängt von Bitrate, Zeitstempel und einem Puffer für Container-Metadaten sowie den Keyframe ab, der Ihrer Sprungposition am nächsten liegt. Eine schnelle Überschlagsformel lautet:
bytes ≈ seconds × bitrate(B/s) + buffer
Mit der Bitrate in Bytes pro Sekunde (1 Mb/s ≈ 125.000 B/s) und einem Puffer von 1 MiB:
# Thumbnail at 10 s from a 5 Mb/s H.264 MP4 stream
BITRATE_BPS=$((5 * 125000)) # 625,000 B/s
SEEK_SECONDS=10
BUFFER=$((1 * 1024 * 1024)) # 1,048,576 B
BYTES_NEEDED=$((SEEK_SECONDS * BITRATE_BPS + BUFFER))
# Bytes_needed = 7,298,576
Das ist nur eine Schätzung: variable Bitraten und Metadaten am Ende können sie hinfällig machen. Nachdem Sie die obige Berechnung ausgeführt haben, fordern Sie dieses Präfix wie folgt an:
set -euo pipefail
mkdir estimated-thumbnail
curl -fsSL -r "0-$((BYTES_NEEDED - 1))" --max-filesize "$BYTES_NEEDED" https://example.com/video.mp4 | \
ffmpeg -nostdin -n -hide_banner -loglevel error -f mp4 -i pipe:0 \
-ss 00:00:10 -frames:v 1 -update 1 -f image2 estimated-thumbnail/thumb.jpg
Den Workflow mit einem Bash-Hilfsskript automatisieren
Speichern Sie dies als thumbnail.sh und führen Sie es mit Bash aus. Es akzeptiert einen Zeitstempel im
Format HH:MM:SS[.ms] und eine geschätzte ganzzahlige Bitrate von 1 bis 999 Mb/s. Das Hilfsskript
wiederholt Downloads in eine temporäre Datei, versucht zunächst das Präfix und lädt bei Bedarf das
vollständige Video herunter. Dieser Fallback kann die Bandbreite und den Speicherplatz der gesamten
Datei beanspruchen.
#!/usr/bin/env bash
set -euo pipefail
VIDEO_URL=${1:-}
TIMESTAMP=${2:-00:00:05} # HH:MM:SS[.ms]
BITRATE_MBPS=${3:-5} # average megabits-per-second
OUT=${4:-thumbnail.jpg}
if [[ -z "$VIDEO_URL" ]]; then
echo "Usage: $0 <url> [timestamp] [bitrate_mbps] [out]" >&2
exit 1
fi
if [[ ! "$TIMESTAMP" =~ ^[0-9]{2}:[0-5][0-9]:[0-5][0-9]([.][0-9]{1,3})?$ ]] ||
[[ ! "$BITRATE_MBPS" =~ ^[1-9][0-9]{0,2}$ ]]; then
echo "Use HH:MM:SS[.ms] and a whole-number bitrate from 1 to 999 Mb/s" >&2
exit 1
fi
if [[ -e "$OUT" || -L "$OUT" ]]; then
echo "Output already exists: $OUT" >&2
exit 1
fi
# Convert timestamp → seconds
IFS=: read -r H M S <<< "$TIMESTAMP"
SEEK_SECONDS=$((10#$H * 3600 + 10#$M * 60 + 10#${S%.*} + 1))
BYTES=$((SEEK_SECONDS * BITRATE_MBPS * 125000 + 1048576))
WORK_DIR=$(mktemp -d "${TMPDIR:-/tmp}/video-thumb.XXXXXX")
trap 'rm -f -- "$WORK_DIR/input.mp4" "$WORK_DIR/thumb.jpg"; rmdir "$WORK_DIR"' EXIT
make_thumbnail() {
rm -f -- "$WORK_DIR/thumb.jpg"
ffmpeg -nostdin -y -hide_banner -loglevel error -xerror \
-ss "$TIMESTAMP" -i "$WORK_DIR/input.mp4" -map 0:v:0 \
-frames:v 1 -q:v 2 -c:v mjpeg -update 1 -f image2 "$WORK_DIR/thumb.jpg" &&
[[ -s "$WORK_DIR/thumb.jpg" ]]
}
if curl -fsSL --retry 3 --proto '=http,https' --proto-redir '=http,https' \
--range "0-$((BYTES - 1))" --max-filesize "$BYTES" \
-o "$WORK_DIR/input.mp4" -- "$VIDEO_URL" && make_thumbnail; then
echo "Thumbnail extracted from the initial download"
else
echo "Retrying with a complete, seekable download" >&2
curl -fsSL --retry 3 --proto '=http,https' --proto-redir '=http,https' \
-o "$WORK_DIR/input.mp4" -- "$VIDEO_URL"
if ! make_thumbnail; then
echo "No thumbnail decoded at $TIMESTAMP" >&2
exit 1
fi
fi
# Refuse to replace an output created by another process during the download.
(set -o noclobber; cat "$WORK_DIR/thumb.jpg" > "$OUT")
echo "Thumbnail saved to $OUT"
Verschiedene Containerformate handhaben
Nicht jede Datei lässt sich aus einem Präfix decodieren. Diese beispielhaften Range-Größen enthalten nicht garantiert die erforderlichen Header und Frames; nutzen Sie für die Automatisierung den Fallback des Hilfsskripts auf den vollständigen Download.
MP4
Verwenden Sie ein Präfix nur, wenn das Atom moov vor den Mediendaten liegt. Ein größeres Präfix
löst das Problem von Metadaten am Ende nicht, es sei denn, es umfasst die gesamte Datei:
set -euo pipefail
mkdir mp4-thumbnail
curl -fsSL -r 0-5242879 https://example.com/video.mp4 | \
ffmpeg -nostdin -n -hide_banner -loglevel error -f mp4 -i pipe:0 -ss 00:00:05 \
-frames:v 1 -update 1 -f image2 mp4-thumbnail/thumb.jpg
WebM
WebM-Header sind schlanker, doch Keyframes können weit auseinanderliegen, weshalb ein größerer Ausschnitt hilft:
set -euo pipefail
mkdir webm-thumbnail
curl -fsSL -r 0-10485759 https://example.com/video.webm | \
ffmpeg -nostdin -n -hide_banner -loglevel error -f webm -i pipe:0 -ss 00:00:05 \
-frames:v 1 -update 1 -f image2 webm-thumbnail/thumb.jpg
MKV
Matroska unterstützt Sprünge, eine Pipe erfordert jedoch weiterhin sequenzielles Decodieren. Das Präfix muss die Track-Header und genügend Cluster enthalten, um den angeforderten Frame zu erreichen:
set -euo pipefail
mkdir mkv-thumbnail
curl -fsSL -r 0-15728639 https://example.com/video.mkv | \
ffmpeg -nostdin -n -hide_banner -loglevel error -f matroska -i pipe:0 -ss 00:00:05 \
-frames:v 1 -update 1 -f image2 mkv-thumbnail/thumb.jpg
Bewährte Verfahren für die Performance
- Ein vollständiger Download kann schneller sein als mehrere fehlgeschlagene Präfix-Anfragen; messen Sie es mit Ihren eigenen Dateien.
- Wählen Sie Präfixgrößen anhand der tatsächlichen Bitrate und des Container-Layouts, nicht allein anhand der Auflösung.
- Wenn Sie mehrere Thumbnails aus einer Datei benötigen, kann der Filter
selectvon FFmpeg mehrere Frames in einem Durchlauf herausziehen:
set -euo pipefail
mkdir selected-thumbnails
curl -fsSL -r 0-15728639 https://example.com/video.mp4 | \
ffmpeg -nostdin -n -hide_banner -loglevel error -f mp4 -i pipe:0 \
-vf "select=eq(n\,150)+eq(n\,300)+eq(n\,450)" -fps_mode vfr -f image2 selected-thumbnails/thumb_%02d.jpg
Anwendungsfälle aus der Praxis
Vorschauraster für Videos
set -euo pipefail
mkdir preview-grid
curl -fsSL -r 0-20971519 https://example.com/video.mp4 | \
ffmpeg -nostdin -n -hide_banner -loglevel error -f mp4 -i pipe:0 \
-vf "select='not(mod(n,300))',scale=160:90,tile=4x3" \
-frames:v 1 -update 1 -f image2 preview-grid/preview.jpg
On-Demand-Thumbnails für Streaming-Plattformen
Ein unverschlüsseltes, unabhängig decodierbares MPEG-TS-HLS-Segment lässt sich direkt lesen. Fragmentierte MP4-Segmente, wie sie DASH und einige HLS-Streams verwenden, benötigen zusätzlich ihr Initialisierungssegment; übergeben Sie FFmpeg in diesen Fällen die Manifest-URL, statt ein isoliertes Mediensegment zu pipen.
set -euo pipefail
mkdir live-thumbnail
curl -fsSL https://example.com/live/segment-123.ts | \
ffmpeg -nostdin -n -hide_banner -loglevel error -f mpegts -i pipe:0 \
-frames:v 1 -update 1 -f image2 live-thumbnail/live_thumb.jpg
Parallele Stapelverarbeitung
GNU Parallel kann das Hilfsskript für mehrere URLs ausführen. Jeder Job erhält einen eigenen Ausgabedateinamen:
parallel --halt soon,fail=1 -j 4 bash ./thumbnail.sh {} 00:00:10 5 thumb_{#}.jpg ::: \
https://cdn.example.com/a.mp4 \
https://cdn.example.com/b.mp4 \
https://cdn.example.com/c.mp4
Häufige Probleme beheben
- Fehlende oder unvollständige Bilder: Nutzen Sie den Fallback auf den vollständigen Download und prüfen Sie, ob der Zeitstempel innerhalb der Videodauer liegt. FFmpeg kann sich erfolgreich beenden, ohne einen Frame zu erzeugen, wenn der angeforderte Zeitstempel hinter dem Ende liegt.
- Langsame Downloads: Nutzen Sie ein nahegelegenes CDN und ermitteln Sie passende Ranges durch
Messungen.
--compressedhandelt die Komprimierung des Response-Body aus, nicht der Header, und hilft bei bereits komprimiertem Video in der Regel nicht. - FFmpeg erkennt das Format nicht: Prüfen Sie auf einen HTTP-Fehler-Body oder fehlende
Metadaten. Das Erzwingen von
-f mp4kann eine abgeschnittene oder nicht frei navigierbare Eingabe nicht reparieren.
Fazit
Byte-Range-Requests können die Download-Kosten für Thumbnails senken, wenn das Dateilayout es zulässt. Prüfen Sie Download- und Decoder-Fehler, stellen Sie sicher, dass ein Bild erzeugt wurde, und behalten Sie einen frei navigierbaren Fallback bei.
Wenn Sie einen fertigen Dienst bevorzugen, werfen Sie einen Blick auf den Robot /video/thumbs von Transloadit. Er übernimmt Formaterkennung, Retries und Skalierung, damit Sie sich auf die Entwicklung Ihres Produkts konzentrieren können.
