ffmpeg ile Telefonda Video Birleştirme: Farklı Oranlardaki Klipleri Tek Vloga Çevirmek
Son güncelleme:
4 dk okuma

Recapday'in ana fikri şu: arkadaş grubu gün içinde belirlediği saatlerde birkaç saniyelik klip çekiyor, gün sonunda hepsi tek bir vloga dönüşüyor.
Kulağa basit geliyor, ama girdi tahmin edilebilir değil. Aynı gün içinde şunların hepsi olabiliyor:
- Dikey çekilmiş 2 saniyelik video
- Yatay çekilmiş 10 saniyelik video
- Tek kare fotoğraf
- Sesli klip ve sessiz klip
Hepsi tek bir 1920×1080 videoda, düzgün görünecek şekilde buluşmak zorunda. Üstelik bu iş telefonda yapılıyor, sunucuda ffmpeg çalıştırmıyorum.
Neden telefonda?
Sunucu tarafında birleştirmek daha kolay olurdu. Üç sebeple yapmadım:
- Maliyet. Paylaşımlı hostingte ffmpeg ya yok ya da bu iş için çok yavaş.
- Depolama. Vlog dosyası sunucuya hiç yüklenmiyor. Bir uygulamada en büyük dosya vlog'sa, onu hiç yüklememek en büyük tasarruf.
- Gizlilik. "Günlük vlog cihazınızdan çıkmaz" cümlesini yazabilmek, gerçekten öyle olduğu için değerli.
Flutter tarafında ffmpeg_kit_flutter_new paketini kullandım.
Adım 1: Her klibi ortak bir çerçeveye oturtmak
Farklı oranlardaki görüntüleri birleştirmenin klasik yolu, kısa kenarları siyah bantla doldurmak. Sonuç kötü görünüyor.
Bunun yerine bulanık dolgu kullandım: aynı görüntünün büyütülüp bulanıklaştırılmış hâli arka plan oluyor, gerçek görüntü ortasına oturuyor. Instagram ve TikTok'ta gördüğünüz efekt.
Filtre zinciri şöyle:
[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]Okunuşu:
split=2: aynı girdiyi iki kola ayırıyor.- Zemin kolu
increaseile çerçeveyi taşacak kadar büyüyor,cropile kesiliyor. Böylece boşluk kalmıyor. gblur=sigma=28bulanıklaştırıyor,eq=brightness=-0.18karartıyor. Karartma olmadan arka plan öndeki görüntüyle yarışıyor.- Ön kol
decreaseile çerçeveye sığacak kadar küçülüyor. overlayortalıyor.
setsar=1 şart. Bazı telefonlar kare olmayan piksel oranıyla kayıt yapıyor; bunu sıfırlamazsanız birleştirmede görüntü yatay olarak eziliyor.
Fotoğraflar aynı zincirden geçiyor, tek fark girişte -loop 1 -t <saniye> olması.
Adım 2: Yazıları geçirmek ve Türkçe karakterler
Her klibin üstünde çeken kişinin adı, çekim saati ve varsa açıklaması var. ffmpeg'in resmî filtre belgelerinde anlatılan drawtext filtresiyle basılıyor.
Burada erken öğrendiğim bir ders var: metni doğrudan filtre metnine yazmayın.
drawtext=text='öğle molası uzadı' ← kırılganFiltre zinciri bir dizgi; içindeki : ' % \ karakterlerinin hepsi kaçış istiyor. Kullanıcının yazdığı bir açıklamada bunların hepsi olabilir. Bir kesme işareti tüm komutu bozuyor.
Çözüm textfile:
drawtext=fontfile=/.../Manrope.ttf:textfile=/.../aciklama.txt:x=76:y=h-th-64Metin geçici bir dosyaya UTF-8 olarak yazılıyor, ffmpeg oradan okuyor. Kaçış sorunu tamamen ortadan kalkıyor.
İkinci nokta: fontfile vermezseniz ffmpeg sistemin varsayılan fontunu kullanıyor ve Türkçe karakterler kutu olarak çıkıyor. Uygulamanın kendi fontunu (Manrope, Fredoka) uygulama paketinden geçici klasöre kopyalayıp yolunu veriyorum.
Adım 3: Klipleri uç uca eklemek
Tüm klipler aynı çözünürlük, aynı kare hızı ve aynı ses formatına normalize edildikten sonra birleştirme ucuz:
-f concat -safe 0 -i liste.txt -c copy cikti.mp4ffmpeg'in format belgelerinde tanımlanan concat demuxer, -c copy ile yeniden kodlama yapmadan çalışıyor, saniyeler sürüyor. Ama yalnızca girdiler gerçekten aynıysa. Tek bir klip farklı kare hızındaysa ses ile görüntü kayıyor. Normalizasyon adımını atlamayın.
Alternatif olan concat filtresi her şeyi yeniden kodluyor ve telefonda çok daha yavaş.
Adım 4: Müzik eklemek
Kullanıcı dosyadan bir müzik seçebiliyor, kliplerin sesiyle müziğin dengesini ayrı ayrı ayarlayabiliyor. Üç sorun var:
Müzik vlogtan kısa olabilir. Girişte döngüye alınıyor:
-stream_loop -1 -i muzik.mp3Ani başlama ve bitme kulağı rahatsız ediyor. Girişte 1 saniye, çıkışta 2 saniye yumuşama:
afade=t=in:st=0:d=1,
afade=t=out:st=<bitiş-2>:d=2İki ses kanalı birleşmeli:
[k][m]amix=inputs=2:duration=first:dropout_transition=0[a]duration=first önemli: müzik döngüde olduğu için süresi sonsuz sayılıyor; first demezseniz çıktı bitmiyor. dropout_transition=0 ise bir kanal sustuğunda diğerinin ani yükselmesini engelliyor.
Adım 5: Sıkıştırma ve ölçmek
Depolama sınırlı olduğu için yüklenen klipler sıkıştırılıyor. Buradaki soru şu: kalite ne kadar düşüyor?
"Gözüme iyi göründü" bir ölçüm değil. ffmpeg'in kendi araçlarıyla ölçtüm:
ffmpeg -i orijinal.mp4 -i sikistirilmis.mp4 \
-lavfi "psnr;[0:v][1:v]ssim" -f null -CRF 23 ve 720p'ye ölçekleme ile gerçek bir kullanıcı klibinde çıkan sonuç:
| Ölçüt | Sonuç | Yorum |
|---|---|---|
| PSNR | 43,35 dB | 40 dB üstü genelde "gözle ayırt edilemez" kabul edilir |
| SSIM | 0,974 | 1,0 mükemmel benzerlik |
Bu sayılar kararı kolaylaştırdı. Dosya boyutu ciddi biçimde düştü, kalite kaybı ölçülebilir biçimde ihmal edilebilir seviyede kaldı.
Öneri: sıkıştırma ayarını "iyi görünüyor" diyerek seçmeyin. İki komut, iki sayı, ve elinizde savunulabilir bir karar oluyor.
Kaçınılması gereken beş şey
- Siyah bantla doldurmak. Bulanık dolgu neredeyse aynı maliyette ve çok daha iyi görünüyor.
drawtext=text=ile kullanıcı metni basmak.textfilekullanın.fontfilevermemek. Türkçe karakterler kutuya dönüyor.- Normalize etmeden
concat -c copyyapmak. Ses görüntüden kayıyor. setsar=1yazmamak. Bazı cihazlarda görüntü eziliyor.
Recapday'in mimarisinin tamamı ve bu kararların bağlamı ayrı bir yazıda.
Sık Sorulan Sorular
Neden sunucuda değil telefonda birleştiriliyor?
Üç sebeple: paylaşımlı hostingte ffmpeg ya yok ya da bu iş için çok yavaş; vlog dosyası sunucuya hiç yüklenmediği için depolama tasarrufu büyük; ve "günlük vlog cihazınızdan çıkmaz" cümlesi gerçekten doğru oluyor.
Farklı oranlardaki klipler nasıl birleştiriliyor?
Siyah bant yerine bulanık dolgu ile: aynı görüntünün büyütülüp bulanıklaştırılmış hâli arka plan oluyor, gerçek görüntü ortasına oturuyor. setsar=1 yazmak şart; bazı telefonlar kare olmayan piksel oranıyla kaydettiği için bu sıfırlanmazsa görüntü yatay olarak eziliyor.
Kullanıcının yazdığı metin drawtext'e nasıl basılır?
text= ile doğrudan değil, textfile ile. Filtre zinciri bir dizgi olduğu için metindeki : ' % ve \ karakterlerinin hepsi kaçış istiyor; tek bir kesme işareti bütün komutu bozuyor.
concat neden bazen sesi görüntüden kaydırıyor?
-c copy ile çalışan concat demuxer yeniden kodlama yapmadığı için yalnızca girdiler gerçekten aynıysa doğru sonuç veriyor. Tek bir klip farklı kare hızındaysa ses kayıyor; normalizasyon adımını atlamamak gerekiyor.
Sıkıştırmanın kaliteyi ne kadar düşürdüğü nasıl ölçülür?
ffmpeg'in kendi psnr ve ssim filtreleriyle. Yazıdaki örnekte CRF 23 ve 720p ölçekleme ile PSNR 43,35 dB, SSIM ise 0,974 çıktı; ikisi de kaybın gözle ayırt edilemez seviyede kaldığını gösteriyor.
İlgili Yazılar
Flutter'da Kamerayı Kullanıcıya Bırakmak: Yön Zorlamadan Video Çekmek
Flutter camera paketinde ters dönen önizleme, kayıt başlarken donma ve kilitlenen kamera: Recapday'de çözdüğüm üç hatanın kodunu anlatıyorum.
Firebase Auth + Kendi PHP Sunucun: Composer Olmadan Token Doğrulama ve Hesap Silme
Kimlik Firebase'de, veri kendi sunucunuzda. Paylaşımlı hostingte composer olmadan RS256 token doğrulaması ve hesap silme akışının kurulumu.
Recapday Sevgili Modu: İki Kişilik Gruba Neden Ayrı Bir Mod Yazdım
Recapday'e sevgili modunu neden ve nasıl eklediğimin hikâyesi: iki kişilik grubun farkı, ayrı uygulama yerine mod kararı ve kasten eklemediklerim.