Das skc:atom.video-Atom rendert native HTML5-Videos mit optionalem Autoplay
per IntersectionObserver, Poster-Überblendung, Custom Controls (Play/Pause +
Vollbild-Expand), Barrierefreiheits-Attributen und strukturierten Daten
(schema.org VideoObject).
Das Atom erwartet ein aufbereitetes Daten-Array vom VideoProcessor
(DataProcessor). Dieser liest einen FAL-Ordner aus und gibt eine normalisierte
Struktur zurück.
Der VideoProcessor ist global in lib.contentElement.dataProcessing eingebunden
und läuft automatisch für alle Content Elements mit gefülltem assets-Feld.
Er liest die bereits verarbeitete files-Variable des FilesProcessor aus
(filesVariable-Modus).
lib.contentElement.dataProcessing {
1760003341 = ot-video
1760003341 {
as = video
filesVariable = files
if.isTrue.field = assets
}
}
Enthält das files-Array keine Videodatei (nur Bilder), beendet sich der
Processor sofort ohne Overhead. Das {video}-Array steht danach in allen
Fluid-Templates zur Verfügung.
dataProcessing {
10 = ot-video
10 {
as = video
folderPath = 1:/videos/mein-video/
# alternativ: Ordnerpfad aus einem Inhalts-Element-Feld
# folderField = tx_myextension_video_folder
}
}
Ein Ordner repräsentiert genau ein Video (Video-Set). Alle Dateien im Ordner gehören zu diesem einen Video: verschiedene Auflösungen, Formate, Poster, Untertitel, Transkript und Metadaten.
/fileadmin/videos/mein-video/
├── video_loop_1080p.mp4 Auflösungssuffix: _360p _480p _720p _1080p _1440p _2160p _4k
├── video_loop_1080p.webm WebM wird pro Auflösung vor MP4 ausgegeben (bessere Kompression)
├── video_loop_720p.mp4 _loop im Dateinamen aktiviert Loop-Modus (siehe unten)
├── video_loop_720p.webm
├── poster.jpg oder .webp / .png — Dateiname "poster" hat Vorrang
├── captions.de.vtt Muster: {kind}.{lang}.vtt
├── captions.en.vtt Kinds: captions, subtitles, chapters, descriptions
├── chapters.de.vtt
├── transcript.de.txt Muster: transcript.{lang}.txt oder transcript.{lang}.md
└── meta.yaml Optionale Metadaten (siehe unten)
Der VideoProcessor erkennt den Auflösungs-Suffix _480p, _720p, _1080p
etc. auch wenn nach der Auflösung noch weitere Zeichen folgen:
video_1080p.mp4 → 1080p erkannt ✓
video_1080p-4mb.mp4 → 1080p erkannt ✓
video_720p-3mb.mp4 → 720p erkannt ✓
Im globalen filesVariable-Modus wählt der Redakteur im Backend nur eine
Videodatei aus. Der VideoProcessor scannt automatisch den gesamten Parent-Ordner
dieser Datei und findet alle weiteren Auflösungen. Der Browser erhält alle
gefundenen Quellen als <source>-Elemente und wählt die passende per
media-Query.
Das Keyword _loop im Dateinamen einer beliebigen Videodatei im Ordner
aktiviert
den Loop-Modus für das gesamte Video-Set:
video_loop_1080p.mp4 → Loop aktiv
video_1080p.mp4 → Loop nicht aktiv
Alternativ kann Loop in meta.yaml gesetzt werden (überschreibt
Dateiname-Erkennung):
loop: true
Die meta.yaml beschreibt das Video-Set semantisch. Sie wird einmal pro Ordner
angelegt und gilt für alle Verwendungen des Videos — unabhängig davon, in
welchem Content Element es eingesetzt wird.
# Metadaten (für schema.org JSON-LD und aria-label)
title: "Produktvorstellung 2025"
description: "Kurze Beschreibung für strukturierte Daten und aria-label."
uploadDate: "2025-03-15"
schemaType: "VideoObject" # VideoObject (Standard), Clip, BroadcastEvent
# Darstellung (Overrides — überschreiben die automatisch erkannten raw-Werte)
aspectRatio: "16:9" # 1:1 | 4:3 | 16:9 | 21:9 — auch "16x9" möglich
duration: "PT2M30S" # ISO 8601 — überschreibt raw.duration
loop: true # Standard: false
decorative: false # Standard: false — rein dekoratives Video (kein Ton, kein Titel)
# Lizenz (optionale Pflichtangabe bei lizenzpflichtigen Videos)
license: "Creative Commons Attribution"
licenseUrl: "https://creativecommons.org/licenses/by/3.0/"
Wenn license gesetzt ist, erscheint ein kleines ©-Icon unten links im Video.
Bei Hover/Focus öffnet sich ein Popup mit dem Lizenztext — bei gesetztem
licenseUrl als anklickbarer Link. Das Icon wird immer angezeigt, auch bei
decorative: true, um Lizenzverstöße zu vermeiden.
Der raw:-Block wird vom CLI-Befehl sitekit:video:process automatisch in die
meta.yaml geschrieben und beim nächsten Lauf aktualisiert. Er enthält
technische
Fakten aus dem Quell-Video:
# --- auto-generated by sitekit:video:process — do not edit below this line ---
raw:
hasAudio: false
aspectRatio: "16:9"
duration: "PT1M28S"
| Feld | Bedeutung |
|---|---|
hasAudio | false wenn das Quell-Video keine Audiospur hat |
aspectRatio | Seitenverhältnis als String (z.B. "16:9") |
duration | Dauer im ISO-8601-Format (z.B. "PT1M28S") |
Priorität: Redaktionelle Werte (aspectRatio, duration) haben Vorrang
über
raw-Werte. hasAudio kann nicht redaktionell überschrieben werden — es
spiegelt
die technische Realität der Quelldatei wider.
Was nicht in meta.yaml gehört:
columns — ist eine Layout-Entscheidung des Templates / Content BlocksshowCaption — hängt vom Render-Kontext ab, nicht vom Video selbstIm filesVariable-Modus (globale Einbindung) können Redakteure Titel und
Beschreibung pro Content-Element-Instanz überschreiben — unabhängig von
meta.yaml.
TYPO3 nutzt dabei eine Checkbox-Logik in sys_file_reference:
sys_file_reference.title | Verhalten |
|---|---|
NULL | Checkbox nicht gesetzt → meta.yaml-Wert wird verwendet |
'' | Checkbox gesetzt, leer gelassen → überschreibt mit leerem String |
'Text' | Checkbox gesetzt mit Wert → überschreibt meta.yaml |
Die Priorität ist: FAL Reference-Level → FAL File-Level → meta.yaml
Die <source media="..."-Queries werden nicht statisch vergeben, sondern vom
VideoSourcesViewHelper anhand der tatsächlichen Elementbreite im Layout
berechnet.
minViewport = resolutionWidth × (12 / columns)
| Auflösung | Element-Schwellwert | columns=12 (Vollbreite) | columns=6 (50 %) | columns=4 (33 %) |
|---|---|---|---|---|
_1080p | 1080 px | (min-width: 1200px) | (min-width: 2160px) | nie geladen |
_720p | 720 px | (min-width: 768px) | (min-width: 1440px) | Fallback |
_480p | 480 px | Fallback | Fallback | Fallback |
Die niedrigste verfügbare Auflösung wird immer ohne media-Attribut
ausgegeben
und dient als sicherer Fallback auf allen Viewports.
columns-ArgumentDas columns-Argument steuert die Berechnung und wird direkt am Atom-Aufruf
mitgegeben. In Textmedia-Layouts wird es automatisch aus dem ot_layout-Feld
abgeleitet:
| ot_layout | columns |
|---|---|
100-media-100-text | 12 |
50-media-50-text | 6 |
33-media-66-text | 4 |
25-media-75-text | 3 |
50-text-50-media | 6 |
66-text-33-media | 4 |
75-text-25-media | 3 |
Für Content Blocks oder eigene Inhaltselemente wird columns direkt am Atom
gesetzt:
<skc:atom.video video="{video}" columns="4" .../>
Der VideoProcessor setzt einen von vier Zuständen:
| Zustand | Video | Poster | Ausgabe |
|---|---|---|---|
ok | ✓ | ✓ | Vollständige Darstellung |
posterMissing | ✓ | ✗ | Video läuft, kein Poster |
videoMissing | ✗ | ✓ | Nur Poster-Bild (statisch) |
error | ✗ | ✗ | SVG-Platzhalter |
Alle Zustände außer ok schreiben einen Eintrag ins TYPO3-System-Log.
| Parameter | Typ | Standard | Beschreibung |
|---|---|---|---|
video | array | — | Pflicht. Ausgabe des VideoProcessor. |
autoplay | boolean | false | Autoplay per IntersectionObserver (JS). Erzwingt muted + playsinline. Im Textmedia-Partial: FAL-Checkbox hat Vorrang über video.autoplay aus meta.yaml. |
loop | boolean | false | Video in Endlosschleife. Automatisch gesetzt wenn _loop im Dateinamen oder loop: true in meta.yaml. |
decorative | boolean | false | Video ist rein dekorativ: aria-hidden="true", kein Titel, kein Transkript, keine strukturierten Daten, keine Custom Controls. Standard aus meta.yaml (decorative: true). |
customControls | boolean | false | Custom Play/Pause-Overlay statt nativer Browser-Controls. Für Autoplay-Videos immer aktiv (WCAG 2.2.2). Wird vom Content Block/Template gesetzt. |
expandable | boolean | false | Expand-Button anzeigen (öffnet beste Qualität im Dialog). Nur wirksam wenn customControls=1 oder autoplay=1. Im Textmedia-Partial: aus data.image_zoom abgeleitet. |
aspectRatio | string | 16x9 | Bootstrap-Ratio-Suffix: 1x1, 4x3, 16x9, 21x9. Standard aus meta.yaml (aspectRatio) bzw. raw.aspectRatio. |
showCaption | boolean | false | Umschließt das Video mit Bootstrap figure/figcaption wenn video.title vorhanden. Ignoriert bei decorative: true. Wird vom Template/Content Block gesetzt, nicht von meta.yaml. |
columns | integer | 12 | Bootstrap-Spaltenanzahl (1–12) für responsive media-Query-Berechnung. 12 = volle Containerbreite. Wird vom Template/Content Block gesetzt, nicht von meta.yaml. |
stackingBreakpoint | string | md | Unterhalb dieses Breakpoints kollabiert das Layout auf 1 Spalte (Vollbreite). Nur relevant wenn columns < 12. |
class | string | '' | Zusätzliche CSS-Klassen auf dem Container. |
Controls-Logik:
| Situation | <video controls> | Custom Overlay |
|---|---|---|
| Standard (kein Autoplay, kein Custom) | ✓ Native Controls | — |
customControls=1 | — | Play/Pause |
customControls=1 + expandable=1 | — | Play/Pause + Expand |
autoplay=1 (immer Custom) | — | Play/Pause (+ Expand wenn expandable=1) |
decorative=1 | — | — |
<skc:atom.video
video="{video}"
autoplay="1"
loop="1"
decorative="1"
aspectRatio="1x1"
/>
Alternativ: alles in meta.yaml definieren, dann reicht am Atom-Aufruf:
<skc:atom.video video="{video}" aspectRatio="1x1"/>
Barrierefreiheit: aria-hidden="true", kein Fokus, respektiert
prefers-reduced-motion.
<skc:atom.video
video="{video}"
aspectRatio="16x9"
columns="6"
/>
<skc:atom.video
video="{video}"
customControls="1"
expandable="1"
aspectRatio="16x9"
columns="12"
/>
Wenn meta.yaml vollständig ist (title, description, uploadDate, poster
vorhanden), wird automatisch ein <script type="application/ld+json"> mit
VideoObject ausgegeben.
<skc:atom.video
video="{video}"
autoplay="1"
aspectRatio="16x9"
columns="12"
/>
Bei vorhandener transcript.de.txt im Ordner wird ein verstecktes <div> mit
aria-describedby ausgegeben — lesbar für Screen Reader, indexierbar für
Google.
<div class="sk-video ratio ratio-16x9 sk-video-autoplay"
data-js="videoPlayer"
data-best-src="/fileadmin/videos/mein-video/video_1080p.mp4"
data-best-src-type="video/mp4">
<!-- Poster-Overlay (blendet aus wenn Video startet) -->
<div class="sk-video-poster" aria-hidden="true">
<img src="/fileadmin/videos/mein-video/poster.jpg"
class="sk-video-poster-img" alt="" loading="eager">
</div>
<!-- Ladeanimation -->
<div class="sk-video-loader" aria-hidden="true">
<div class="sk-video-spinner"></div>
</div>
<video class="sk-video-element" preload="none" loop muted playsinline
aria-label="Produktvorstellung 2025">
<source src="/fileadmin/.../video_1080p.webm" type="video/webm"
media="(min-width: 1200px)">
<source src="/fileadmin/.../video_1080p.mp4" type="video/mp4"
media="(min-width: 1200px)">
<source src="/fileadmin/.../video_720p.webm" type="video/webm">
<source src="/fileadmin/.../video_720p.mp4" type="video/mp4">
</video>
<!-- Custom Controls -->
<div class="sk-video-controls" aria-label="Video-Steuerung">
<button class="sk-video-btn sk-video-btn-play" data-js="videoPlay"
type="button" aria-label="Abspielen / Pausieren">
<span class="sk-video-icon sk-video-icon-play" aria-hidden="true">...</span>
<span class="sk-video-icon sk-video-icon-pause" aria-hidden="true">...</span>
</button>
<button class="sk-video-btn sk-video-btn-expand" data-js="videoExpand"
type="button" aria-label="In Vollbild öffnen">
...
</button>
</div>
</div>
data-best-src enthält die höchste verfügbare Auflösung (WebM bevorzugt,
sonst MP4). Der Expand-Button öffnet diese Quelle im Dialog — unabhängig davon,
welche <source> der Browser für die eingebettete Ansicht gewählt hat.
<div class="sk-video ratio ratio-16x9"
data-js="videoPlayer"
data-best-src="/fileadmin/videos/mein-video/video_1080p.mp4"
data-best-src-type="video/mp4">
<video class="sk-video-element" preload="none"
aria-label="Produktvorstellung 2025">
<source src="/fileadmin/.../video_1080p.webm" type="video/webm"
media="(min-width: 2160px)">
<source src="/fileadmin/.../video_1080p.mp4" type="video/mp4"
media="(min-width: 2160px)">
<source src="/fileadmin/.../video_720p.webm" type="video/webm">
<source src="/fileadmin/.../video_720p.mp4" type="video/mp4">
</video>
<div class="sk-video-controls" aria-label="Video-Steuerung">
<button class="sk-video-btn sk-video-btn-play" data-js="videoPlay" ...>
<button class="sk-video-btn sk-video-btn-expand"
data-js="videoExpand" ...>
</div>
</div>
1080p wird praktisch nie geladen — 720p ist bei 660 px Elementbreite ausreichend.
<div class="sk-video ratio ratio-16x9" role="img"
aria-label="Video nicht verfügbar">
<!-- Inline SVG Platzhalter -->
<svg viewBox="0 0 16 9" class="sk-video-placeholder" aria-hidden="true">
...
</svg>
</div>
Datei:Build/Default/src/js/Components/Video/initVideoPlayer.js
Selector: [data-js="videoPlayer"]
| Ereignis | Aktion |
|---|---|
| Element betritt Viewport (≥ 25 %) | is-loading setzen, preload="auto" starten (nur Autoplay-Videos) |
canplay-Event | video.play() aufrufen |
| Erfolgreiches Play | is-loading entfernen, is-playing setzen → Poster blendet aus |
| Klick auf Container | Pause / Weiter (nur Autoplay-Videos; nicht-Autoplay: Play-Button verwenden) |
| Klick auf Play/Pause-Button | Pause / Weiter (alle Videos) |
| Klick auf Expand-Button | Inline-Video pausieren, <dialog> mit bester Quelle am gleichen Timestamp öffnen |
| Dialog schließen (Taste / Klick) | Dialog-Position ins Inline-Video übernehmen, Dialog entfernen |
prefers-reduced-motion: reduce | Autoplay wird übersprungen, Poster bleibt |
| Ladefehler (404, Codec) | is-loading entfernen |
Der Expand-Button öffnet ein natives <dialog>-Element mit dem Video aus
data-best-src (höchste Qualität). Das Video startet an der gleichen Position
wie das Inline-Video. Nach dem Schließen (ESC, X-Button oder Klick aufs
Backdrop)
wird die aktuelle Position zurück ins Inline-Video übertragen.
| Klasse | Bedeutung |
|---|---|
.sk-video | Basis-Container (Bootstrap .ratio ist ebenfalls gesetzt) |
.sk-video-autoplay | Autoplay-Modus aktiv |
.sk-video.is-loading | Video puffert — Spinner sichtbar |
.sk-video.is-playing | Video läuft — Poster wird ausgeblendet, Controls ausgeblendet (Autoplay) |
| Klasse | Bedeutung |
|---|---|
.sk-video-element | Das <video>-Element |
.sk-video-poster | Poster-Overlay (nur Autoplay) |
.sk-video-poster-img | <img> innerhalb des Poster-Overlays |
.sk-video-loader | Spinner-Container |
.sk-video-spinner | Rotierender CSS-Spinner |
.sk-video-placeholder | SVG-Platzhalter im Error-Zustand |
| Klasse | Bedeutung |
|---|---|
.sk-video-controls | Controls-Overlay (immer sichtbar, außer Autoplay läuft und kein Hover) |
.sk-video-btn | Basis-Klasse für alle Control-Buttons |
.sk-video-btn-play | Play/Pause-Button |
.sk-video-btn-expand | Expand-Button (öffnet Dialog) |
.sk-video-icon-play | Play-Icon (sichtbar wenn is-playing fehlt) |
.sk-video-icon-pause | Pause-Icon (sichtbar wenn is-playing gesetzt) |
| Klasse | Bedeutung |
|---|---|
.sk-video-dialog | <dialog>-Element für Vollbild-Ansicht |
.sk-video-dialog-inner | Innerer Flex-Container |
.sk-video-dialog-element | <video>-Element im Dialog |
.sk-video-dialog-close | X-Button zum Schließen des Dialogs |
| Anforderung | Umsetzung |
|---|---|
| WCAG 1.4.2 Audio Control | muted-Attribut bei Autoplay und dekorativen Videos — kein Ton |
| WCAG 2.2.2 Pause, Stop, Hide | Klick auf Container (Autoplay) oder Play-Button pausiert / startet; prefers-reduced-motion deaktiviert Autoplay |
| WCAG 1.2.2 Captions | <track kind="captions"> via captions.{lang}.vtt im Ordner |
| WCAG 1.2.3 Audio Description | <track kind="descriptions"> via descriptions.{lang}.vtt |
| Dekoratives Video | aria-hidden="true", tabindex="-1", keine Custom Controls — Screen Reader ignorieren das Element |
| Informatives Video | aria-label aus meta.yaml title, aria-describedby auf Transkript-Div |
JSON-LD wird automatisch ausgegeben wenn:
decorative ist falsemeta.yaml enthält: title, description, uploadDate{
"@context": "https://schema.org",
"@type": "VideoObject",
"name": "Produktvorstellung 2025",
"description": "Kurze Beschreibung ...",
"thumbnailUrl": "https://example.com/fileadmin/videos/mein-video/poster.jpg",
"uploadDate": "2025-03-15",
"duration": "PT2M30S",
"contentUrl": "https://example.com/fileadmin/videos/mein-video/video_1080p.webm",
"transcript": "Hier steht das vollständige Transkript ..."
}
| Datei | Beschreibung |
|---|---|
packages/ot-sitekit-base/Classes/DataProcessing/VideoProcessor.php | Daten-Aufbereitung aus FAL-Ordner, Loop-Erkennung, FAL-Override, bestSrc |
packages/ot-sitekit-base/Classes/ViewHelpers/VideoSourcesViewHelper.php | Berechnet responsive media-Queries anhand von columns |
packages/ot-sitekit-base/Resources/Private/Components/Bootstrap5/Atom/Video/Video.html | Fluid-Komponente |
packages/ot-sitekit-base/Resources/Private/ContentElements/Bootstrap5/Partials/Media/Type/Video.html | Fluid-Partial, leitet columns aus ot_layout ab, kombiniert FAL + meta.yaml |
Build/Default/src/js/Components/Video/initVideoPlayer.js | IntersectionObserver-Autoplay, Custom Controls, Expand-Dialog |
Build/Default/src/scss/Components/_Video.scss | CSS-Zustände, Animationen, Controls, Dialog |
packages/ot-sitekit-base/Resources/Private/Language/locallang.xlf | Übersetzungsschlüssel video.error.notAvailable |
Der Befehl sitekit:video:process scannt einen konfigurierten Ordner nach
Video-Sets und generiert fehlende Varianten (Auflösungen, Poster). Er arbeitet
vollständig auf Dateisystem-Ebene — kein FAL, kein TYPO3-Frontend erforderlich.
Der Befehl benötigt ffmpeg für Transkodierung und Poster-Extraktion.
ffmpeg -version
Ist ffmpeg nicht verfügbar, gibt der Befehl eine klare Fehlermeldung aus und
beendet sich. Ein --dry-run funktioniert auch ohne ffmpeg (Pre-flight-Checks
laufen trotzdem durch).
In .ddev/config.yaml eintragen:
webimage_extra_packages:
- ffmpeg
Danach DDEV neu starten:
ddev restart
Überprüfen:
ddev exec ffmpeg -version
sudo apt-get update
sudo apt-get install -y ffmpeg
Überprüfen:
ffmpeg -version
ffprobe -version
ffprobe wird zusammen mit ffmpeg installiert und wird vom Command für
Videoinformationen (Auflösung, Dauer) benötigt.
H.264 / AAC (Standard, MP4): Im Ubuntu-Paket ffmpeg aus den
Universe-Repositories enthalten — keine zusätzlichen Pakete nötig.
VP9 / Opus (WebM, optional): Ebenfalls im Standard-ffmpeg-Paket enthalten,
sofern Ubuntu 20.04+ verwendet wird.
ffmpeg-Pfad prüfen:
which ffmpeg
# → /usr/bin/ffmpeg
Ist ffmpeg nicht im $PATH des Webserver-Prozesses erreichbar, den absoluten
Pfad in der Projekt-Config eintragen:
ffmpegBinary: "/usr/bin/ffmpeg"
Vor jeder Ausführung (auch --dry-run) prüft der Befehl automatisch:
| Prüfung | Fehler-Verhalten |
|---|---|
| ffmpeg vorhanden? | Abbruch mit Hinweis auf Installation (außer --dry-run) |
| Config-Datei lesbar? | Abbruch |
rootFolder existiert? | Abbruch mit aufgelöstem Pfad |
| Quell-Videos lesbar? | Video-Set überspringen, Warnung ausgeben |
| Freier Speicherplatz | Abbruch wenn Schätzung > verfügbarer Platz (außer --force-disk) |
Für jedes Video-Set, das Dateien generieren würde, schätzt der Befehl den benötigten Speicherplatz anhand der konfigurierten Bitraten und der Quell-Video-Dauer:
Schätzung pro Datei = (videoBitrate + audioBitrate) × Dauer ÷ 8
Die Gesamtschätzung wird mit einem Puffer von 20 % aufgeschlagen und gegen den freien Speicherplatz auf dem Ziel-Dateisystem geprüft. Bei zu wenig Platz bricht der Befehl vor der ersten Generierung ab.
[ERROR] sitekit:video:process — Pre-flight fehlgeschlagen
✗ ffmpeg nicht gefunden
→ ddev exec apt-get install -y ffmpeg
→ oder: ffmpegBinary in VideoProcessing.yaml setzen
✗ Zu wenig Speicherplatz
→ Benötigt: ~1.2 GB (Schätzung)
→ Verfügbar: 0.8 GB
→ Tipp: --force-disk überspringt diese Prüfung (auf eigene Gefahr)
⚠ rootFolder nicht gefunden: fileadmin/user_upload/Videos/
Geprüfte Pfade:
/var/www/html/fileadmin/user_upload/Videos/ (nicht gefunden)
/var/www/html/public/fileadmin/user_upload/Videos/ (nicht gefunden)
→ Ordner anlegen oder rootFolder in Config anpassen
ddev exec typo3 sitekit:video:process
Mit Projekt-Config:
ddev exec typo3 sitekit:video:process --config config/VideoProcessing.yaml
Nur anzeigen, was generiert würde (kein Schreibzugriff, kein ffmpeg nötig):
ddev exec typo3 sitekit:video:process --dry-run
Bestehende Dateien neu generieren:
ddev exec typo3 sitekit:video:process --force
Speicherplatz-Prüfung überspringen (Vorsicht):
ddev exec typo3 sitekit:video:process --force-disk
Einzelnen Unterordner verarbeiten:
ddev exec typo3 sitekit:video:process --folder fileadmin/user_upload/Videos/mein-video/
vendor/bin/typo3 sitekit:video:process --config config/VideoProcessing.yaml
| Option | Beschreibung |
|---|---|
--config | Pfad zur Konfigurations-YAML. Standard: Extension-Default (siehe unten). |
--folder | Überschreibt rootFolder aus der Config — verarbeitet nur diesen einen Unterordner. |
--dry-run | Zeigt alle geplanten Aktionen, schreibt keine Dateien. Funktioniert ohne ffmpeg. |
--force | Generiert auch Dateien neu, die bereits existieren. |
--force-disk | Überspringt die Speicherplatz-Prüfung. |
--poster-only | Generiert nur Poster — überspringt Auflösungsvarianten. Mit --force auch vorhandene Poster. |
--poster-at=N | Extrahiert das Poster bei Position N in Sekunden (z.B. 5 oder 12.5). Überschreibt poster.frame aus der Config. |
# Poster aus Frame 2 (Config-Standard) neu erzwingen:
ddev exec typo3 sitekit:video:process --poster-only --force
# Poster bei Sekunde 8 extrahieren (überschreibt vorhandenes Poster):
ddev exec typo3 sitekit:video:process --poster-only --force --poster-at=8
# Poster bei 1 min 23,5 sec (= 83,5 Sekunden):
ddev exec typo3 sitekit:video:process --poster-only --force --poster-at=83.5
# Nur für einen bestimmten Ordner:
ddev exec typo3 sitekit:video:process --poster-only --force --poster-at=5 --folder=fileadmin/videos/mein-video/
EXT:ot_sitekitbase/Configuration/VideoProcessing.yaml
Diese Datei enthält sinnvolle Defaults und wird automatisch geladen, wenn kein
--config-Argument übergeben wird.
Für projektspezifische Einstellungen eine eigene YAML-Datei anlegen und beim Aufruf übergeben:
ddev exec typo3 sitekit:video:process --config config/VideoProcessing.yaml
Die Projekt-Config wird mit der Extension-Default-Config zusammengeführt (Deep Merge) — es müssen nur abweichende Werte angegeben werden.
# Ordner, der nach Video-Unterordnern durchsucht wird.
# Ein Unterordner gilt als Video-Set, wenn er mindestens eine Videodatei enthält.
#
# Pfadauflösung (in dieser Reihenfolge):
# 1. Absoluter Pfad → direkt verwenden
# 2. Relativer Pfad → {projectRoot}/{path}
# 3. Relativer Pfad (nicht gefunden) → {projectRoot}/public/{path}
# (Composer-Setup mit public/ als Webroot)
rootFolder: ""
# ffmpeg-Pfad. Leer = Suche im $PATH.
ffmpegBinary: ""
poster:
enabled: true
# Frame-Nummer für die Poster-Extraktion.
# 1 = erstes Frame, 2 = zweites Frame (sicherer bei Videos die schwarz beginnen).
# Intern wird ffmpeg mit "-vf select=eq(n\,{frame-1})" aufgerufen.
frame: 2
format: jpg # jpg | webp
quality: 85 # JPEG-Qualität (1–100) bzw. WebP-Qualität
overwrite: false # true = bestehende poster.jpg überschreiben
resolutions:
# H.264 (MP4):
# crf: Constant Rate Factor — niedriger = besser. Typisch: 18–28, Standard 21–23.
# maxrate: Peak-Bitrate (z.B. "4000k"). bufsize = 2× maxrate empfohlen.
# twoPass: true = zweimal encodieren, bessere Bitratenverteilung. ~2× langsamer.
# preset: ultrafast … veryslow — beeinflusst Geschwindigkeit vs. Dateigröße.
#
# VP9 (WebM):
# crfWebm: VP9-CRF. Niedriger = besser. Typisch: 28–40, Standard 31–35.
# deadlineWebm: good | best | realtime (good = guter Kompromiss Geschwindigkeit/Qualität)
# twoPassWebm: true = empfohlen für VP9, trifft Bitrate genauer.
#
# Quellen ohne Audiospur werden automatisch erkannt — kein Audio in der Ausgabe.
# Der Befehl skaliert nie hoch: Auflösung wird übersprungen, wenn Quelle kleiner ist.
- height: 1080
suffix: 1080p
crf: 21
maxrate: "4000k"
bufsize: "8000k"
audioBitrate: "192k"
preset: slow
twoPass: false
crfWebm: 31
deadlineWebm: good
twoPassWebm: false
- height: 720
suffix: 720p
crf: 23
maxrate: "2500k"
bufsize: "5000k"
audioBitrate: "128k"
preset: slow
twoPass: false
crfWebm: 33
deadlineWebm: good
twoPassWebm: false
- height: 480
suffix: 480p
crf: 24
maxrate: "1200k"
bufsize: "2400k"
audioBitrate: "96k"
preset: slow
twoPass: true
crfWebm: 35
deadlineWebm: good
twoPassWebm: true
formats:
mp4: true # H.264 + AAC — breite Kompatibilität, immer empfohlen
webm: false # VP9 + Opus — bessere Kompression (~30 % kleiner als H.264)
# Benötigt libvpx in ffmpeg (im Standard-Ubuntu-Paket enthalten)
# Leere meta.yaml anlegen, wenn keine vorhanden ist.
# Enthält alle Felder als kommentierte Platzhalter.
metaYamlSkeleton: true
WebM ist standardmäßig deaktiviert. Um es in einer Projekt-Config einzuschalten (nur geänderte Werte angeben):
formats:
webm: true
WebM verwendet den VP9-Codec mit Opus-Audio — beides im Standard-ffmpeg-Paket
auf Ubuntu 20.04+ enthalten. VP9-Dateien sind typischerweise ~30 % kleiner als H.264 bei
gleicher Qualität.
Für jeden Unterordner unter rootFolder:
poster.jpg / poster.webp? → Frame aus Quell-Video extrahieren_720p.mp4, _480p.mp4 etc. fehlen? → transkodieren.webm-VariantenmetaYamlSkeleton: true) — nur wenn keine vorhandenraw:-Block aktualisieren — technische Fakten aus dem Quell-Video ermitteln und eintragenNach jeder Verarbeitung schreibt der Befehl einen raw:-Block ans Ende der
meta.yaml. Dieser Block enthält automatisch erkannte technische Fakten und wird
bei jedem Lauf neu generiert (Änderungen unterhalb des Markers gehen verloren):
# --- auto-generated by sitekit:video:process — do not edit below this line ---
raw:
hasAudio: false
aspectRatio: "16:9"
duration: "PT1M28S"
| Feld | Quelle | Verwendung im VideoProcessor |
|---|---|---|
hasAudio | ffprobe -select_streams a:0 | Kein Audio-Tag und keine Audio-Fehlermeldung im Frontend |
aspectRatio | ffprobe width / height | Fallback wenn kein aspectRatio im redaktionellen Teil |
duration | ffprobe duration-Sekunden | Fallback wenn keine duration im redaktionellen Teil |
Redaktionelle Werte im oberen Teil der meta.yaml haben immer Vorrang über
die raw:-Werte. hasAudio kann nicht redaktionell überschrieben werden.
Bereits vorhandene Dateien werden übersprungen (außer --force ist gesetzt).
[DRY-RUN] sitekit:video:process
Pre-flight
✓ ffmpeg 7.1.3 gefunden
✓ Speicherplatz: ~1.8 GB benötigt, 48 GB verfügbar
mein-video/
✓ poster.jpg vorhanden
✓ video_1080p.mp4 vorhanden (1080p)
→ video_1080p.webm wird generiert (1080p, vp9 crf=31, good)
→ video_720p.mp4 wird generiert (720p, crf=23, max=2500k, slow)
→ video_720p.webm wird generiert (720p, vp9 crf=33, good)
→ video_480p.mp4 wird generiert (480p, crf=24, max=1200k, slow, 2-pass)
→ video_480p.webm wird generiert (480p, vp9 crf=35, good, 2-pass)
→ meta.yaml Skeleton wird angelegt
hintergrund-loop/
→ poster.jpg wird generiert (Frame 2)
✓ loop_1080p.mp4 vorhanden (1080p)
→ loop_1080p.webm wird generiert (1080p, vp9 crf=31, good, kein Ton)
✓ meta.yaml vorhanden
2 Video-Sets · 7 Dateien würden generiert
Der Befehl übernimmt den Basisnamen der gefundenen Quelldatei und ersetzt den Auflösungs-Suffix:
Quelle: mein-video_1080p.mp4
Generiert: mein-video_720p.mp4
mein-video_480p.mp4
mein-video_1080p.webm (wenn formats.webm: true)
mein-video_720p.webm
mein-video_480p.webm
Existiert noch kein Auflösungs-Suffix im Quelldateinamen, wird er angehängt:
Quelle: interview.mp4
Generiert: interview_1080p.mp4 (falls Quelle ≥ 1080p)
interview_720p.mp4
interview_1080p.webm (wenn formats.webm: true)
MP4 und WebM werden getrennt geprüft: Eine vorhandene _720p.mp4 verhindert
nicht die Generierung von _720p.webm — und umgekehrt.
Der rootFolder-Wert aus der Config wird wie folgt aufgelöst:
| Eingabe | Aufgelöst als |
|---|---|
/var/www/html/fileadmin/... | Absoluter Pfad, direkt verwendet |
fileadmin/user_upload/Videos/ | {projectRoot}/fileadmin/... |
fileadmin/user_upload/Videos/ | {projectRoot}/public/fileadmin/... (Fallback) |
Der Befehl prüft beide relativen Varianten und gibt einen Fehler aus, wenn keiner der Pfade existiert.
| Datei | Beschreibung |
|---|---|
packages/ot-sitekit-base/Classes/Command/VideoProcessCommand.php | CLI-Command |
packages/ot-sitekit-base/Configuration/SiteKit/VideoProcessing.yaml | Extension-Default-Config |
public/fileadmin/Dokumentation/de/Atom_Video.md | Doku: Video-Atom und VideoProcessor |