Video-Pipeline
Inhalt
Videos durchlaufen nach dem Upload einen einzigen BullMQ-Job namens transcode. Die fünf Stufen unten sind Schritte innerhalb dieses Jobs, keine eigenen Jobs. Im Code laufen die Thumbnails erst nach der Transcodierung. Der Status wechselt von PENDING über PROCESSING auf LIVE, bei einem Fehler auf FAILED.
Überblick: Pipeline-Stufen
[1] Eingang → [2] Analyse → [3] Thumbnails → [4] Transcodierung → [5] Abschluss
| Stufe | Name | Zuständigkeit |
|---|---|---|
| 1 | Eingang | Datei empfangen, in MinIO ablegen |
| 2 | Analyse | Metadaten mit ffprobe extrahieren |
| 3 | Thumbnails | Kandidaten-Frames generieren |
| 4 | Transcodierung | HLS-Varianten mit FFmpeg erzeugen |
| 5 | Abschluss | Status setzen, Clients benachrichtigen |
Stufe 1: Eingang
Der Browser schickt die Datei in einem einzigen Request per XMLHttpRequest an POST /api/upload, die API streamt den Body direkt nach MinIO. Fortsetzbare Uploads (tus.io) sind noch nicht umgesetzt, ein abgebrochener Upload startet von vorn.
- Den Upload-Fortschritt misst der Browser selbst über
xhr.upload.onprogress - Vorab vergleicht die API den
Content-Length-Header mitMAX_UPLOAD_SIZE_MB(Standard 8192) und antwortet bei Überschreitung mit 413 - Die Datei wird per Magic-Bytes als Video geprüft und landet in MinIO unter
play-raw/[videoId].[ext] - Danach legt die API den Datensatz mit Status
PENDINGund SichtbarkeitPRIVATEan und reiht den Jobtranscodeein
Stufe 2: Analyse
ffprobe extrahiert die technischen Metadaten der Rohdatei:
| Metadatum | Beschreibung |
|---|---|
| Dauer | Länge des Videos in Sekunden |
| Auflösung | Breite x Höhe in Pixeln |
| Codec | Video-Codec (z. B. h264, av1) |
| Bitrate | Durchschnittliche Bitrate in kbit/s |
Die extrahierten Werte werden in der Datenbank gespeichert und bestimmen, welche HLS-Qualitätsstufen in Stufe 4 erzeugt werden.
Stufe 3: Thumbnails
FFmpeg extrahiert 5 Thumbnail-Kandidaten aus gleichmäßig verteilten Zeitpunkten im Video. Der Nutzer kann anschließend im Studio einen davon als Titelbild auswählen oder ein eigenes Bild hochladen.
- Die Kandidaten entstehen bei 10, 30, 50, 70 und 90 % der Laufzeit und werden als WebP unter
play-thumbs/[videoId]-cand-[1-5].webpgespeichert - Die Breite ist 1280 px, die Höhe folgt dem Seitenverhältnis
- Voreingestellt ist der mittlere Kandidat (50 %)
- Die Auswahl des Nutzers wird in der Datenbank vermerkt
Stufe 4: Transcodierung
FFmpeg erzeugt einen HLS-Stream mit mehreren Qualitätsvarianten (Adaptive Bitrate Streaming).
Qualitätsvarianten
| Auflösung | Bitrate | Bedingung |
|---|---|---|
| 1080p | max. 5 Mbit/s | Nur wenn Quelle >= 1080p |
| 720p | max. 2,8 Mbit/s | Nur wenn Quelle >= 720p |
| 480p | max. 1,2 Mbit/s | Immer, auch bei kleineren Quellen |
Die Leiter ist im Worker fest hinterlegt. TRANSCODE_QUALITIES steht zwar in .env.example und im Unraid-Template, wird vom Code aber nicht ausgelesen. Video wird mit H.264 (libx264, Preset medium, CRF 22) kodiert, Audio mit AAC 128 kbit/s. Mit TRANSCODE_EXTRA_CODECS=av1 und/oder vp9 rendert der Worker pro Stufe zusätzlich AV1 (libsvtav1) oder VP9 (libvpx-vp9) mit rund 30 % weniger Bitrate. Das kostet deutlich mehr CPU, der Standard ist leer.
HLS-Konfiguration
- Segmentformat: fmp4 (fragmentiertes MP4)
- Segmentlänge: 4 Sekunden
- Playlist:
master.m3u8+ je eine Playlist pro Qualitätsstufe - Ausgabe: MinIO
play-videos/[videoId]/
Der Job meldet seinen Fortschritt in groben Schritten an BullMQ (10, 20, je Variante bis 80, dann 85, 95 und 100 %). Das Studio zeigt davon keinen Prozentwert, sondern fragt den Status alle zwei Sekunden ab, solange ein Video PENDING oder PROCESSING ist.
Stufe 5: Abschluss
Nach erfolgreicher Transcodierung:
- DB-Status des Videos wird auf
LIVEgesetzt, dazu Dauer, Auflösung, Codec, Bitrate, Thumbnails und das Format (SHORTbei Hochformat bis 60 Sekunden, sonstLONG) - Ist
AUTO_CAPTIONS_ENABLED=1, wird der Untertitel-Job eingereiht - Ist das Video öffentlich, bekommen Abonnenten eine In-App-Benachrichtigung
- Das Studio sieht den neuen Status beim nächsten Abruf
Externer Import (yt-dlp)
Nutzer können externe Video-URLs importieren (YouTube, Vimeo und andere yt-dlp-kompatible Plattformen).
Ablauf:
- Nutzer gibt URL im Studio ein
- Worker führt
yt-dlpaus und lädt die Originaldatei herunter - Titel und Beschreibung aus yt-dlp werden in den Datensatz übernommen
- Standard-Sichtbarkeit ist
privat— Nutzer muss aktiv veröffentlichen - Danach durchläuft das Video die normale 5-stufige Pipeline
Auto-Untertitel (Whisper) — hinter Feature-Flag
Die Integration von whisper.cpp ermöglicht automatische Untertitelgenerierung nach der Transcodierung.
- Aktivierung über
AUTO_CAPTIONS_ENABLED=1, Standard ist aus - Der Transcode-Job übergibt fest die Sprache
de, eine automatische Spracherkennung ist nicht verdrahtet - Modell über
WHISPER_MODEL, Standard/models/ggml-small.bin - Ausgabe als WebVTT-Spur am Video (
VideoCaption) - Laut Code werden nur die ersten sechs HLS-Segmente der ersten Variante transkribiert, bei 4-Sekunden-Segmenten also etwa die ersten 24 Sekunden
- Läuft als eigene Queue
caption-generatemit Concurrency 1
Wiedergabe
Der HLS.js-Player übernimmt die Videowiedergabe im Browser.
- Adaptive Bitraten-Umschaltung (ABR) basierend auf Bandbreite und Puffer
- Eine manuelle Qualitätsauswahl ist im Player-Code nicht zu finden
- Die HLS-Dateien liegen im öffentlich lesbaren Bucket
play-videosund werden über/s3/ausgeliefert. Signierte URLs gibt es nicht, die Sichtbarkeit prüft die API beim Abruf der Videodaten
Speicher-Layout (MinIO)
play-raw/
[videoId].[ext] ← Original (bleibt erhalten, Trim nutzt es erneut)
play-videos/
[videoId]/
master.m3u8 ← HLS-Master-Playlist
1080p/
playlist.m3u8
init.mp4
seg-000.m4s
seg-001.m4s
...
720p/
playlist.m3u8
init.mp4
seg-000.m4s
...
480p/ ...
play-thumbs/
[videoId]-cand-1.webp ← Kandidaten bei 10/30/50/70/90 %
...
[videoId]-cand-5.webp
play-assets/
logo/<uuid>.png ← Instanz-Logo
captions/[videoId]/[lang].vtt