Videos auf dem Handy mit ffmpeg zusammenfügen: gemischte Seitenverhältnisse in einem Vlog
Zuletzt aktualisiert:
5 Min. Lesezeit

Die Grundidee von Recapday ist diese: eine Freundesgruppe nimmt zu selbst gewählten Zeiten ein paar Sekunden auf, und am Ende des Tages wird alles zu einem Vlog.
Klingt einfach, aber die Eingabe ist unvorhersehbar. An einem einzigen Tag kann alles davon zusammenkommen:
- Ein hochkant aufgenommener 2-Sekunden-Clip
- Ein quer aufgenommener 10-Sekunden-Clip
- Ein einzelnes Foto
- Clips mit und ohne Ton
Alles muss sich in einem 1920×1080-Video treffen und dabei gut aussehen. Und das läuft auf dem Handy — ffmpeg auf einem Server nutze ich nicht.
Warum auf dem Handy?
Serverseitig zusammenzufügen wäre einfacher gewesen. Drei Gründe dagegen:
- Kosten. Auf Shared Hosting ist ffmpeg entweder nicht verfügbar oder dafür viel zu langsam.
- Speicher. Die Vlog-Datei wird nie hochgeladen. Ist die größte Datei einer App der Vlog, ist ihn gar nicht erst hochzuladen die größte mögliche Ersparnis.
- Datenschutz. Den Satz "der Tages-Vlog verlässt dein Gerät nie" schreiben zu können, ist gerade deshalb wertvoll, weil er stimmt.
Auf der Flutter-Seite nutze ich das Paket ffmpeg_kit_flutter_new.
Schritt 1: jeden Clip in einen gemeinsamen Rahmen setzen
Der klassische Weg, unterschiedliche Seitenverhältnisse zu kombinieren, ist schwarzer Rand an den kurzen Seiten. Das sieht schlecht aus.
Stattdessen nutze ich unscharfe Ränder: eine vergrößerte, weichgezeichnete Kopie desselben Bildes wird zum Hintergrund, das echte Bild sitzt mittig darauf. Der Effekt, den man von Instagram und TikTok kennt.
Die Filterkette:
[0:v]split=2[zemin][on];
[zemin]scale=1920:1080:force_original_aspect_ratio=increase,
crop=1920:1080,
gblur=sigma=28,
eq=brightness=-0.18[bulanik];
[on]scale=1920:1080:force_original_aspect_ratio=decrease[sigan];
[bulanik][sigan]overlay=(W-w)/2:(H-h)/2,
fps=30,setsar=1,
format=yuv420p[v]Gelesen:
split=2teilt dieselbe Eingabe in zwei Zweige.- Der Hintergrundzweig skaliert mit
increase, sodass er den Rahmen überfüllt, undcropschneidet zu. So bleibt keine Lücke. gblur=sigma=28zeichnet weich,eq=brightness=-0.18dunkelt ab. Ohne das Abdunkeln konkurriert der Hintergrund mit dem Vordergrund.- Der Vordergrundzweig skaliert mit
decrease, sodass er in den Rahmen passt. overlayzentriert.
setsar=1 ist Pflicht. Manche Handys nehmen mit nicht-quadratischem Pixelseitenverhältnis auf; ohne Zurücksetzen wird das Ergebnis horizontal gestaucht.
Fotos laufen durch dieselbe Kette, nur mit -loop 1 -t <Sekunden> am Eingang.
Schritt 2: den Text einbrennen — samt Sonderzeichen
Über jedem Clip stehen der Name, die Aufnahmezeit und eine optionale Bildunterschrift, gezeichnet mit dem drawtext-Filter, den die offizielle ffmpeg-Filterdokumentation beschreibt.
Hier habe ich früh eine Lektion gelernt: Nutzertext niemals direkt in den Filterstring schreiben.
drawtext=text='öğle molası uzadı' ← brüchigDie Filterkette ist eine einzige Zeichenkette, und : ' % \ darin müssen alle maskiert werden. Eine Bildunterschrift kann jedes davon enthalten. Ein einziges Apostroph zerlegt den ganzen Befehl.
Die Lösung heißt textfile:
drawtext=fontfile=/.../Manrope.ttf:textfile=/.../untertitel.txt:x=76:y=h-th-64Der Text landet als UTF-8 in einer temporären Datei, ffmpeg liest ihn von dort. Das Maskierungsproblem verschwindet vollständig.
Zweiter Punkt: ohne fontfile greift ffmpeg auf eine Systemschrift zurück, und Sonderzeichen erscheinen als Kästchen. Ich kopiere die eigenen Schriften der App (Manrope, Fredoka) aus dem Bundle in ein temporäres Verzeichnis und übergebe den Pfad.
Schritt 3: die Clips aneinanderhängen
Sind alle Clips auf dieselbe Auflösung, Bildrate und dasselbe Audioformat normalisiert, ist das Zusammenfügen billig:
-f concat -safe 0 -i liste.txt -c copy ausgabe.mp4Der in der ffmpeg-Formatdokumentation beschriebene concat-Demuxer kodiert mit -c copy nicht neu — das dauert Sekunden. Aber nur, wenn die Eingaben wirklich übereinstimmen. Ein Clip mit abweichender Bildrate, und der Ton läuft aus dem Bild. Den Normalisierungsschritt nicht überspringen.
Der alternative concat-Filter kodiert alles neu und ist auf einem Handy deutlich langsamer.
Schritt 4: Musik hinzufügen
Man kann einen Titel aus den eigenen Dateien wählen und ihn gegen den Clip-Ton abmischen. Drei Probleme:
Der Titel kann kürzer sein als der Vlog. Am Eingang in Schleife legen:
-stream_loop -1 -i musik.mp3Abruptes Anfangen und Enden ist unangenehm. Eine Sekunde ein, zwei Sekunden aus:
afade=t=in:st=0:d=1,
afade=t=out:st=<Ende-2>:d=2Die beiden Tonspuren müssen sich mischen:
[k][m]amix=inputs=2:duration=first:dropout_transition=0[a]duration=first ist wichtig: die geloopte Musik gilt als unendlich lang, ohne first endet die Ausgabe nie. dropout_transition=0 verhindert, dass eine Spur laut aufspringt, wenn die andere still wird.
Schritt 5: Kompression — und messen
Speicher ist endlich, hochgeladene Clips werden also komprimiert. Bleibt die Frage: wie viel Qualität hat das gekostet?
"Sah für mich gut aus" ist keine Messung. ffmpeg kann es beantworten:
ffmpeg -i original.mp4 -i komprimiert.mp4 \
-lavfi "psnr;[0:v][1:v]ssim" -f null -Bei CRF 23 mit Herunterskalierung auf 720p, an einem echten Nutzerclip:
| Maß | Ergebnis | Lesart |
|---|---|---|
| PSNR | 43,35 dB | Über 40 dB gilt allgemein als visuell nicht unterscheidbar |
| SSIM | 0,974 | 1,0 wäre exakte Übereinstimmung |
Diese Zahlen machten die Entscheidung leicht. Die Dateigröße sank deutlich, der Qualitätsverlust blieb messbar vernachlässigbar.
Empfehlung: wählen Sie Ihre Kompressionseinstellung nicht mit "sieht gut aus". Zwei Befehle, zwei Zahlen — und eine Entscheidung, die sich verteidigen lässt.
Fünf Dinge, die man vermeiden sollte
- Schwarze Ränder. Unscharfe Ränder kosten fast dasselbe und sehen weit besser aus.
- Nutzertext über
drawtext=text=.textfileverwenden. fontfileweglassen. Sonderzeichen werden zu Kästchen.concat -c copyohne Normalisierung. Der Ton läuft aus dem Bild.setsar=1vergessen. Auf manchen Geräten wird das Bild gestaucht.
Die vollständige Architektur von Recapday und der Zusammenhang dieser Entscheidungen stehen in einem eigenen Beitrag.
Häufig gestellte Fragen
Warum wird auf dem Handy statt auf dem Server zusammengefügt?
Aus drei Gründen: Auf Shared Hosting fehlt ffmpeg oder ist dafür viel zu langsam; die Vlog-Datei wird gar nicht erst hochgeladen, was die größte Ersparnis beim Speicher ist; und der Satz "Ihr Tagesvlog verlässt Ihr Gerät nicht" stimmt dann tatsächlich.
Wie werden Clips mit unterschiedlichen Seitenverhältnissen zusammengefügt?
Mit einer unscharfen Füllung statt schwarzer Balken: Eine vergrößerte, weichgezeichnete Kopie desselben Bildes bildet den Hintergrund, das echte Bild sitzt mittig darauf. setsar=1 ist Pflicht, denn manche Handys nehmen mit nicht-quadratischen Pixeln auf und das Bild wird sonst horizontal gestaucht.
Wie übergibt man selbst geschriebenen Text an drawtext?
Über textfile, niemals direkt über text=. Die Filterkette ist eine Zeichenkette, also müssen : ' % und \ im Text alle maskiert werden — ein einziges Apostroph zerlegt den ganzen Befehl.
Warum läuft der Ton bei concat manchmal aus dem Takt?
Der concat-Demuxer mit -c copy kodiert nicht neu und liefert deshalb nur dann korrekte Ergebnisse, wenn die Eingaben wirklich identisch sind. Ein einziger Clip mit abweichender Bildrate lässt den Ton verrutschen; der Normalisierungsschritt darf nicht ausgelassen werden.
Wie misst man, wie viel Qualität die Komprimierung kostet?
Mit den ffmpeg-eigenen Filtern psnr und ssim. Im Beispiel dieses Beitrags ergaben CRF 23 und die Skalierung auf 720p einen PSNR von 43,35 dB und einen SSIM von 0,974 — beides Werte für einen mit bloßem Auge nicht erkennbaren Verlust.
Verwandte Artikel
Flutter-Kamera ohne erzwungene Ausrichtung: drei Fehler und ihre Lösungen
Gedrehte Vorschau, Einfrieren beim Aufnahmestart, tote Kamera: drei Flutter-Kamerafehler aus Recapday und der Code, der sie behoben hat.
Firebase Auth mit eigenem PHP-Server: Token-Prüfung und Kontolöschung ohne Composer
Identität bei Firebase, Daten auf dem eigenen Server: RS256-Token-Prüfung in PHP ohne Composer und der verlangte Kontolöschungs-Ablauf.
Recapday Paar-Modus: Warum eine Zweiergruppe einen eigenen Modus brauchte
Wie der Paar-Modus in Recapday entstand: was sich bei zwei Personen ändert, warum ein Modus besser war als eine zweite App und was ich bewusst wegließ.