İçeriğe geç / Skip to content / Zum Inhalt

Videos auf dem Handy mit ffmpeg zusammenfügen: gemischte Seitenverhältnisse in einem Vlog

Ahmet Balaman

Zuletzt aktualisiert:

5 Min. Lesezeit

MobilffmpegFlutterDartRecapdayVideobearbeitungKompression
Videos auf dem Handy mit ffmpeg zusammenfügen: gemischte Seitenverhältnisse in einem Vlog

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:

  1. Kosten. Auf Shared Hosting ist ffmpeg entweder nicht verfügbar oder dafür viel zu langsam.
  2. 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.
  3. Datenschutz. Den Satz "der Tages-Vlog verlässt dein Gerät nie" schreiben zu können, ist gerade deshalb wertvoll, weil er stimmt.

Die Vlog-Kette am Handy: Clips, gemeinsamer Rahmen, Text, Verbinden, Musik und Komprimieren

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=2 teilt dieselbe Eingabe in zwei Zweige.
  • Der Hintergrundzweig skaliert mit increase, sodass er den Rahmen überfüllt, und crop schneidet zu. So bleibt keine Lücke.
  • gblur=sigma=28 zeichnet weich, eq=brightness=-0.18 dunkelt ab. Ohne das Abdunkeln konkurriert der Hintergrund mit dem Vordergrund.
  • Der Vordergrundzweig skaliert mit decrease, sodass er in den Rahmen passt.
  • overlay zentriert.

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

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

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

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

Abruptes 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=2

Die 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

  1. Schwarze Ränder. Unscharfe Ränder kosten fast dasselbe und sehen weit besser aus.
  2. Nutzertext über drawtext=text=. textfile verwenden.
  3. fontfile weglassen. Sonderzeichen werden zu Kästchen.
  4. concat -c copy ohne Normalisierung. Der Ton läuft aus dem Bild.
  5. setsar=1 vergessen. 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.

Kommentare