Atom: Video

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).


Voraussetzung: VideoProcessor

Das Atom erwartet ein aufbereitetes Daten-Array vom VideoProcessor (DataProcessor). Dieser liest einen FAL-Ordner aus und gibt eine normalisierte Struktur zurück.

Globale Einbindung via lib.contentElement (Standard)

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.

Alternative: Expliziter Ordnerpfad

dataProcessing {
    10 = ot-video
    10 {
        as = video
        folderPath = 1:/videos/mein-video/
        # alternativ: Ordnerpfad aus einem Inhalts-Element-Feld
        # folderField = tx_myextension_video_folder
    }
}

Ordner-Konvention

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)

Auflösungssuffix

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  ✓

Automatische Mehrauflösungs-Erkennung (filesVariable-Modus)

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.

Loop-Modus via Dateiname

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

meta.yaml

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.

Redaktioneller Bereich (editierbar)

# 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.

raw-Block (automatisch, nicht manuell bearbeiten)

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"
FeldBedeutung
hasAudiofalse wenn das Quell-Video keine Audiospur hat
aspectRatioSeitenverhältnis als String (z.B. "16:9")
durationDauer 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 Blocks
  • showCaption — hängt vom Render-Kontext ab, nicht vom Video selbst
  • CSS-Klassen, TCA-Felder, Auflösungs-Filter

FAL-Override: Titel und Beschreibung

Im 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.titleVerhalten
NULLCheckbox 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


Responsive Auflösungsauswahl

Die <source media="..."-Queries werden nicht statisch vergeben, sondern vom VideoSourcesViewHelper anhand der tatsächlichen Elementbreite im Layout berechnet.

Formel

minViewport = resolutionWidth × (12 / columns)
AuflösungElement-Schwellwertcolumns=12 (Vollbreite)columns=6 (50 %)columns=4 (33 %)
_1080p1080 px(min-width: 1200px)(min-width: 2160px)nie geladen
_720p720 px(min-width: 768px)(min-width: 1440px)Fallback
_480p480 pxFallbackFallbackFallback

Die niedrigste verfügbare Auflösung wird immer ohne media-Attribut ausgegeben und dient als sicherer Fallback auf allen Viewports.

columns-Argument

Das 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_layoutcolumns
100-media-100-text12
50-media-50-text6
33-media-66-text4
25-media-75-text3
50-text-50-media6
66-text-33-media4
75-text-25-media3

Für Content Blocks oder eigene Inhaltselemente wird columns direkt am Atom gesetzt:


<skc:atom.video video="{video}" columns="4" .../>

Zustände (state)

Der VideoProcessor setzt einen von vier Zuständen:

ZustandVideoPosterAusgabe
okVollständige Darstellung
posterMissingVideo läuft, kein Poster
videoMissingNur Poster-Bild (statisch)
errorSVG-Platzhalter

Alle Zustände außer ok schreiben einen Eintrag ins TYPO3-System-Log.


Parameter

ParameterTypStandardBeschreibung
videoarrayPflicht. Ausgabe des VideoProcessor.
autoplaybooleanfalseAutoplay per IntersectionObserver (JS). Erzwingt muted + playsinline. Im Textmedia-Partial: FAL-Checkbox hat Vorrang über video.autoplay aus meta.yaml.
loopbooleanfalseVideo in Endlosschleife. Automatisch gesetzt wenn _loop im Dateinamen oder loop: true in meta.yaml.
decorativebooleanfalseVideo ist rein dekorativ: aria-hidden="true", kein Titel, kein Transkript, keine strukturierten Daten, keine Custom Controls. Standard aus meta.yaml (decorative: true).
customControlsbooleanfalseCustom Play/Pause-Overlay statt nativer Browser-Controls. Für Autoplay-Videos immer aktiv (WCAG 2.2.2). Wird vom Content Block/Template gesetzt.
expandablebooleanfalseExpand-Button anzeigen (öffnet beste Qualität im Dialog). Nur wirksam wenn customControls=1 oder autoplay=1. Im Textmedia-Partial: aus data.image_zoom abgeleitet.
aspectRatiostring16x9Bootstrap-Ratio-Suffix: 1x1, 4x3, 16x9, 21x9. Standard aus meta.yaml (aspectRatio) bzw. raw.aspectRatio.
showCaptionbooleanfalseUmschließ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.
columnsinteger12Bootstrap-Spaltenanzahl (1–12) für responsive media-Query-Berechnung. 12 = volle Containerbreite. Wird vom Template/Content Block gesetzt, nicht von meta.yaml.
stackingBreakpointstringmdUnterhalb dieses Breakpoints kollabiert das Layout auf 1 Spalte (Vollbreite). Nur relevant wenn columns < 12.
classstring''Zusätzliche CSS-Klassen auf dem Container.

Controls-Logik:

Situation<video controls>Custom Overlay
Standard (kein Autoplay, kein Custom)✓ Native Controls
customControls=1Play/Pause
customControls=1 + expandable=1Play/Pause + Expand
autoplay=1 (immer Custom)Play/Pause (+ Expand wenn expandable=1)
decorative=1

Aufruf-Beispiele

Dekoratives Autoplay-Video (ersetzt ein Bild)


<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.


Informatives Video mit 50 % Breite (native Controls mit Seeking)


<skc:atom.video
    video="{video}"
    aspectRatio="16x9"
    columns="6"
/>

Video mit Custom Controls und Expand-Button (z.B. eigener Content Block)


<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.


Informatives Autoplay-Video mit Transkript


<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.


Generiertes HTML (vereinfacht)

Autoplay-Zustand, Vollbreite (columns=12)


<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.

Nicht-Autoplay, 50 % Breite (columns=6)


<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.

Fehler-Zustand (error)


<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>

JavaScript-Verhalten

Datei:Build/Default/src/js/Components/Video/initVideoPlayer.js

Selector: [data-js="videoPlayer"]

EreignisAktion
Element betritt Viewport (≥ 25 %)is-loading setzen, preload="auto" starten (nur Autoplay-Videos)
canplay-Eventvideo.play() aufrufen
Erfolgreiches Playis-loading entfernen, is-playing setzen → Poster blendet aus
Klick auf ContainerPause / Weiter (nur Autoplay-Videos; nicht-Autoplay: Play-Button verwenden)
Klick auf Play/Pause-ButtonPause / Weiter (alle Videos)
Klick auf Expand-ButtonInline-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: reduceAutoplay wird übersprungen, Poster bleibt
Ladefehler (404, Codec)is-loading entfernen

Expand-Dialog

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.


CSS-Klassen und Zustände

Container und Zustandsklassen

KlasseBedeutung
.sk-videoBasis-Container (Bootstrap .ratio ist ebenfalls gesetzt)
.sk-video-autoplayAutoplay-Modus aktiv
.sk-video.is-loadingVideo puffert — Spinner sichtbar
.sk-video.is-playingVideo läuft — Poster wird ausgeblendet, Controls ausgeblendet (Autoplay)

Video-Elemente

KlasseBedeutung
.sk-video-elementDas <video>-Element
.sk-video-posterPoster-Overlay (nur Autoplay)
.sk-video-poster-img<img> innerhalb des Poster-Overlays
.sk-video-loaderSpinner-Container
.sk-video-spinnerRotierender CSS-Spinner
.sk-video-placeholderSVG-Platzhalter im Error-Zustand

Custom Controls

KlasseBedeutung
.sk-video-controlsControls-Overlay (immer sichtbar, außer Autoplay läuft und kein Hover)
.sk-video-btnBasis-Klasse für alle Control-Buttons
.sk-video-btn-playPlay/Pause-Button
.sk-video-btn-expandExpand-Button (öffnet Dialog)
.sk-video-icon-playPlay-Icon (sichtbar wenn is-playing fehlt)
.sk-video-icon-pausePause-Icon (sichtbar wenn is-playing gesetzt)

Expand-Dialog

KlasseBedeutung
.sk-video-dialog<dialog>-Element für Vollbild-Ansicht
.sk-video-dialog-innerInnerer Flex-Container
.sk-video-dialog-element<video>-Element im Dialog
.sk-video-dialog-closeX-Button zum Schließen des Dialogs

Barrierefreiheit

AnforderungUmsetzung
WCAG 1.4.2 Audio Controlmuted-Attribut bei Autoplay und dekorativen Videos — kein Ton
WCAG 2.2.2 Pause, Stop, HideKlick 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 Videoaria-hidden="true", tabindex="-1", keine Custom Controls — Screen Reader ignorieren das Element
Informatives Videoaria-label aus meta.yaml title, aria-describedby auf Transkript-Div

Strukturierte Daten (schema.org)

JSON-LD wird automatisch ausgegeben wenn:

  • decorative ist false
  • meta.yaml enthält: title, description, uploadDate
  • Ein Poster-Bild im Ordner vorhanden ist
{
    "@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 ..."
}

Zugehörige Dateien

DateiBeschreibung
packages/ot-sitekit-base/Classes/DataProcessing/VideoProcessor.phpDaten-Aufbereitung aus FAL-Ordner, Loop-Erkennung, FAL-Override, bestSrc
packages/ot-sitekit-base/Classes/ViewHelpers/VideoSourcesViewHelper.phpBerechnet responsive media-Queries anhand von columns
packages/ot-sitekit-base/Resources/Private/Components/Bootstrap5/Atom/Video/Video.htmlFluid-Komponente
packages/ot-sitekit-base/Resources/Private/ContentElements/Bootstrap5/Partials/Media/Type/Video.htmlFluid-Partial, leitet columns aus ot_layout ab, kombiniert FAL + meta.yaml
Build/Default/src/js/Components/Video/initVideoPlayer.jsIntersectionObserver-Autoplay, Custom Controls, Expand-Dialog
Build/Default/src/scss/Components/_Video.scssCSS-Zustände, Animationen, Controls, Dialog
packages/ot-sitekit-base/Resources/Private/Language/locallang.xlfÜbersetzungsschlüssel video.error.notAvailable

CLI: sitekit:video:process

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.


Voraussetzungen

ffmpeg

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).

ffmpeg in DDEV installieren

In .ddev/config.yaml eintragen:

webimage_extra_packages:
  - ffmpeg

Danach DDEV neu starten:

ddev restart

Überprüfen:

ddev exec ffmpeg -version

ffmpeg auf Ubuntu-Server installieren (Produktion)

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"

Pre-flight-Checks

Vor jeder Ausführung (auch --dry-run) prüft der Befehl automatisch:

PrüfungFehler-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 SpeicherplatzAbbruch wenn Schätzung > verfügbarer Platz (außer --force-disk)

Speicherplatz-Schätzung

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.

Beispiel-Ausgabe bei fehlenden Voraussetzungen

[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

Ausführung

In DDEV (Standard)

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/

Lokal (ohne DDEV)

vendor/bin/typo3 sitekit:video:process --config config/VideoProcessing.yaml

Optionen

OptionBeschreibung
--configPfad zur Konfigurations-YAML. Standard: Extension-Default (siehe unten).
--folderÜberschreibt rootFolder aus der Config — verarbeitet nur diesen einen Unterordner.
--dry-runZeigt alle geplanten Aktionen, schreibt keine Dateien. Funktioniert ohne ffmpeg.
--forceGeneriert auch Dateien neu, die bereits existieren.
--force-diskÜberspringt die Speicherplatz-Prüfung.
--poster-onlyGeneriert nur Poster — überspringt Auflösungsvarianten. Mit --force auch vorhandene Poster.
--poster-at=NExtrahiert das Poster bei Position N in Sekunden (z.B. 5 oder 12.5). Überschreibt poster.frame aus der Config.

Poster neu generieren (Beispiele)

# 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/

Konfiguration

Standard-Config (Extension-Default)

EXT:ot_sitekitbase/Configuration/VideoProcessing.yaml

Diese Datei enthält sinnvolle Defaults und wird automatisch geladen, wenn kein --config-Argument übergeben wird.

Projekt-Config (Überschreibung)

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.

Vollständige Config-Referenz

# 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 aktivieren

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.


Was der Befehl pro Video-Set tut

Für jeden Unterordner unter rootFolder:

  1. Pre-flight — ffmpeg, Lesbarkeit, Speicherplatz prüfen
  2. Quell-Video ermitteln — höchste vorhandene Auflösung als Ausgangsbasis
  3. Poster prüfen — fehlt poster.jpg / poster.webp? → Frame aus Quell-Video extrahieren
  4. Auflösungen prüfen — welche _720p.mp4, _480p.mp4 etc. fehlen? → transkodieren
  5. WebM prüfen (wenn aktiviert) — gleiche Logik für .webm-Varianten
  6. meta.yaml anlegen (wenn metaYamlSkeleton: true) — nur wenn keine vorhanden
  7. meta.yaml raw:-Block aktualisieren — technische Fakten aus dem Quell-Video ermitteln und eintragen

raw-Block in meta.yaml

Nach 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"
FeldQuelleVerwendung im VideoProcessor
hasAudioffprobe -select_streams a:0Kein Audio-Tag und keine Audio-Fehlermeldung im Frontend
aspectRatioffprobe width / heightFallback wenn kein aspectRatio im redaktionellen Teil
durationffprobe duration-SekundenFallback 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).

Beispiel-Ausgabe (dry-run, MP4 + WebM aktiviert)

[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

Dateinamen-Konvention

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.


Pfadauflösung

Der rootFolder-Wert aus der Config wird wie folgt aufgelöst:

EingabeAufgelö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.


Zugehörige Dateien

DateiBeschreibung
packages/ot-sitekit-base/Classes/Command/VideoProcessCommand.phpCLI-Command
packages/ot-sitekit-base/Configuration/SiteKit/VideoProcessing.yamlExtension-Default-Config
public/fileadmin/Dokumentation/de/Atom_Video.mdDoku: Video-Atom und VideoProcessor