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

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

Ahmet Balaman
ffmpegFlutterVideoVideobearbeitungAnilogMobile EntwicklungKompressionDart

Die Grundidee von Anılog 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.

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.

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 concat-Demuxer mit -c copy kodiert 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 Anılog und der Zusammenhang dieser Entscheidungen stehen in einem eigenen Beitrag.

Kommentare