Bu pipeline'ı codec'lerle oynamak istediğim için kurmadım. Animasyonlu görsellerin, pratikte kısa ve sessiz video olan içeriği sunmanın pahalı bir yoluna dönüşmesi yüzünden kurdum.
Workload'ım çoğunlukla birkaç saniyelik animated WebP, GIF ve APNG dosyalarından oluşuyor: onlarca görüntülenen frame ve yüksek temporal redundancy. Trafik mobil ağırlıklı, aynı asset'ler tekrar tekrar sunuluyor ve encoding süresinin maliyeti, dosyanın ömrü boyunca aktarılacak byte'lardan çok daha az önemli.
“Animasyonu MP4'e çevir” basit görünüyor. Production'da öyle değil. Animated image tek FPS'li tam frame dizisi olmak zorunda değil; partial rectangle, disposal kuralları, alpha blending, düzensiz delay, sıfır duration, farklı orientation ve yanıltıcı timing metadata içerebilir.
Bu yüzden pipeline bir FFmpeg komutundan değil, invariants zincirinden oluşuyor:
animated WebP / GIF / APNG
↓
gerçekte görüntülenen canvas state'lerini decode edip yeniden oluştur
↓
source timeline'ı çıkar
↓
source başına CFR seç
↓
upscale yapmadan geometriyi normalize et
↓
konservatif H.264 segment encode et
↓
her source için bağımsız tekrarla
↓
uyumlu segmentleri stream copy ile concat et
↓
final packet timeline'ı normalize et
↓
probe + packet kontrolü + tam decode
↓
atomik yayınla
İlk kural: depolanan dikdörtgenleri değil, gösterilen frame'leri çıkar
Animated WebP, APNG ve GIF küçük frame bölgeleri ile blend/disposal kuralları kullanabilir. Depolanan frame, önceki canvas olmadan tek başına anlamlı olmayabilir.
Bu yüzden extraction birimim doğru viewer'ın gerçekten göstereceği tam composited canvas state. Yanlış compose edilen bir frame H.264'e girdikten sonra sonraki encoder ayarları onu düzeltemez.
Timing source verisidir; FPS tahmin değildir
- Animated WebP frame duration'ı 1 ms biriminde tutar.
- GIF delay'i saniyenin yüzde biri olarak tutar.
- APNG numerator/denominator kullanır; denominator 0 ise 100 kabul edilir.
Gerçek bir WebP örneği 1264×720, 49 görünen frame, sırayla 62/63 ms delay ve toplam 3,063 saniyeydi: yaklaşık 16 fps. Probe ise 25 fps bildirdi.
Doğru source of truth per-frame delay'lerdi.
CFR seçmeden önce bozuk timing'i normalize ediyorum
WebP zero ve çok küçük duration değerlerinin implementation-dependent olabileceğini belirtiyor. GIF zero delay'e izin veriyor, APNG de zero numerator kullanabiliyor.
Ben millisecond precision koruyorum, minimum 10 ms uyguluyorum ve sadece anlamlı timing yoksa 100 ms fallback kullanıyorum. Bu sayılar evrensel değil; asıl nokta geçerli source timing'i default ile ezmemek.
Neden her şeyi 30 fps yapmıyorum
10, 12, 15, 16, 18, 20, 24, 25, 30 fps
Her source için gerçek timeline'ı en düşük maliyetle düzgün temsil eden CFR seçiliyor; 30 fps fallback. 62/63 ms örneği 16 fps'e doğal olarak oturuyor çünkü frame başına 62,5 ms.
Her source farklı CFR kullanabilir
source A → 12 fps CFR H.264 segment
source B → 16 fps CFR H.264 segment
source C → 24 fps CFR H.264 segment
source D → 30 fps CFR H.264 segment
segments → stream-copy concat → tek MP4
Segmentler ayrı ayrı CFR, final dosya ise parça parça CFR ve genel olarak variable-rate olabilir. Tek global FPS için ikinci encode yapmıyorum.
Neden 90 kHz time base
| FPS | 90 kHz'te tam frame duration |
|---|---|
| 10 | 9000 ticks |
| 12 | 7500 ticks |
| 15 | 6000 ticks |
| 16 | 5625 ticks |
| 18 | 5000 ticks |
| 20 | 4500 ticks |
| 24 | 3750 ticks |
| 25 | 3600 ticks |
| 30 | 3000 ticks |
Tüm adaylar 90.000'i tam böler. Böylece packet duration için exact integer grid kullanabiliyorum.
Geometriyi upscale yapmadan normalize ediyorum
| Sınıf | Limit |
|---|---|
| Landscape | 1280×720 |
| Portrait | 720×1280 |
| Mixed orientation | yaklaşık 960×960 canvas |
Küçük source büyütülmez. Farklı aspect ratio'lar stretch edilmez; ortak canvas içine fit edilir ve padding kullanılır. Background siyah, alpha bu background üzerine flattened.
H.264 MP4 neden bu workload'da genellikle daha ucuz
GIF, APNG ve animated WebP'nin kendi optimizasyonları var. Ancak H.264 inter prediction, reference pictures, P-frames ve B-frames için tasarlanmış bir video codec. Static background ve küçük hareketli bölgeleri olan illustrated clips için bu yapı çok iyi çalışıyor.
Apple static web video için H.264 MP4 öneriyor ve animated GIF yerine MP4 kullanılmasını söylüyor; Safari dokümanında GIF'in modern video codec'e göre 12 kata kadar daha fazla bandwidth ve yaklaşık iki kat enerji kullanabileceği belirtiliyor. Bu 12× benim benchmark'ım değil.
Lossy animated WebP zaten sıkıştırılmıştır; H.264'e dönüşüm ikinci lossy generation anlamına gelir. Bu yüzden görsel kalite kontrolü şart.
H.264 profili bilerek sıkıcı
| Ayar | Değer |
|---|---|
| Container | MP4 |
| Codec | H.264 / libx264 |
| Sample entry | avc1 |
| Profile / level | Main @ 3.1 |
| Pixel format | 8-bit yuv420p |
| Rate control | CRF 28 |
| Preset | veryslow |
| Tune | animation |
| Refs | 4 |
| B-frames | 5 |
| Open GOP | off |
| B-pyramid | normal |
| GOP | yaklaşık 5 saniye |
| VBV | 4M / 8M |
| Color | BT.709 limited |
| Timescale | 90,000 |
| Audio/subtitles/data | yok |
Main@3.1 modern cihazların sınırı değil, konservatif single-file envelope. Android 6.0'dan itibaren Main Profile decode zorunlu ve 1280×720@30 fps H.264 HD önerisi olarak veriliyor. Apple static web video için H.264 MP4 öneriyor.
CPU'yu bir kez harcıyorum
-preset veryslow
Encode bir kez, delivery çok kez olur. Bu yüzden x264 CPU harcayabilir. CRF 28 de illustrated ve bandwidth-first workload'ıma özeldir.
Her animasyon bağımsız bir segment olur
ffmpeg -framerate "$SELECTED_FPS" -i frame-%06d.png \
-c:v libx264 -preset veryslow -tune animation -crf 28 \
-profile:v main -level:v 3.1 -pix_fmt yuv420p -tag:v avc1 \
-refs 4 -bf 5 -g "$GOP_FRAMES" -maxrate:v 4M -bufsize:v 8M \
-x264-params "stitchable=1:open-gop=0:b-pyramid=normal:nal-hrd=none" \
-color_range tv -color_primaries bt709 -color_trc bt709 -colorspace bt709 \
-fps_mode passthrough -map_metadata -1 -map_chapters -1 \
-an -sn -dn -video_track_timescale 90000 -movie_timescale 90000 \
-t "$EXPECTED_DURATION" segment.mp4
Frame timeline ve geometry daha önce hazırlanır. GOP yaklaşık selectedFps * 5.
Concat sıkı segment contract sayesinde çalışır
FFmpeg concat uyumlu stream'ler ister ve bir dosyanın duration'ını sonraki dosyanın başlangıcı için kullanır. Bu nedenle uyumluluk concat sırasında değil önce sağlanır.
ffmpeg -f concat -safe 0 -i segments.ffconcat \
-c:v copy -an -sn -dn -movflags +faststart final.mp4
İkinci lossy encode yok.
Stream copy tek başına perfect timestamp demek değil
90000 / 24 = 3750 ticks
Validator gerçek bir output'ta 3751 tick packet buldu. Görsel olarak önemsiz olabilir, fakat exact grid invariant'ını bozuyordu.
Final concat artık setts ile PTS, DTS ve duration'ı bilinen 90-kHz grid'e normalize ediyor ve -c:v copy korunuyor.
Validation encoder'ın parçasıdır
Stream, profile/level, pixel format, dimensions, time base, frame/packet count, exact duration, PTS/DTS, final duration ve full decode kontrol edilir.
ffprobe -v error -select_streams v:0 -show_streams -show_packets -of json final.mp4
ffmpeg -v error -xerror -err_detect explode -i final.mp4 -f null -
Pipeline'ın önlediği hatalar
- Visible canvas yerine raw subframe encode etmek.
- FPS tahminine güvenmek.
- Her şeyi 30 fps yapmak.
- Upscale.
- Aspect ratio stretch.
- Concat sonrası re-encode.
-c copy'yi doğru timing sanmak.- Full decode yapmamak.
Bilerek korumadığım şeyler
Alpha flattened, arbitrary VFR source başına CFR ile yaklaşık temsil edilir, yüksek resolution küçültülür, lossy WebP başka bir lossy generation alır ve audio yoktur. Bunlar gizli değil, design contract'ın parçasıdır.
Bu yaklaşımı ne zaman kullanmam
Transparency korunmalıysa, exact irregular timing kritikse, archival master ise veya zaten adaptive multi-codec stack varsa bu exact pipeline'ı kullanmam. Zaten çok küçük bir animated WebP için de önce benchmark yaparım.
Bugünkü pratik pipeline
- Animation metadata oku.
- Tam visible canvas state'lerini reconstruct et.
- Delay'leri sanitize edip ms timeline kur.
- 10/12/15/16/18/20/24/25/30 arasından source CFR seç.
- No-upscale geometry, padding, alpha flatten.
- H.264 Main@3.1/yuv420p/avc1,
veryslow, CRF 28. - 90-kHz time base.
- Her segmenti validate et.
-c:v copyconcat.- Final timeline normalize.
- Packet inspection ve full decode.
- Sonra publish.
Asıl ders: asset timeline'dır
Animated WebP, GIF ve APNG sadece “çok sayıda resim” değildir; zamanlanmış canvas state dizileridir. Bunu source of truth kabul ettiğimde hareket icat etmeden cadence'i, piksel icat etmeden resolution'ı düşürebildim, source başına CFR kullanabildim ve ek lossy generation olmadan concat yapabildim.
H.264 dosyaları küçülttü, MP4 delivery'yi kolaylaştırdı. Codec kolay kısımdı; gerçek mühendislik animasyonun anlamını representation değişirken korumaktı.