بلاگ پر واپس جائیں
13 اگست، 2026Sergei Solod9 منٹ پڑھنے کا وقت

Animated WebP، GIF اور APNG کو چھوٹے، browser-safe H.264 MP4 میں کیسے تبدیل کرتا ہوں

میری production pipeline واقعی دکھائے گئے canvas states اور source timing کو reconstruct کرتی ہے، ہر source کے لیے CFR چنتی ہے، upscale سے بچتی ہے، independent H.264 segments بناتی ہے، دوسری lossy encode کے بغیر concat کرتی ہے اور publish سے پہلے exact packet timeline validate کرتی ہے۔

H.264FFmpegMP4Animated WebPGIFAPNGویڈیو کمپریشنمیڈیا پائپ لائن

میں نے یہ pipeline نئے codecs آزمانے کے لیے نہیں بنائی۔ مسئلہ زیادہ عملی تھا: Animated WebP، GIF اور APNG اکثر حقیقت میں چند سیکنڈ کی silent video کا کام کر رہے تھے، مگر بار بار web پر deliver کرنے کے لیے ہمیشہ سب سے کم خرچ representation نہیں تھے۔

میرے workload میں animations عموماً مختصر، چند درجن displayed frames پر مشتمل اور temporal redundancy سے بھرپور ہوتے ہیں۔ Encoding ایک بار ہوتی ہے، delivery بار بار؛ اس لیے encode speed کے مقابلے میں final bytes اور predictable browser playback زیادہ اہم ہیں۔

“animation کو MP4 بنا دو” آسان جملہ ہے۔ Production version مشکل ہے۔ Animated image لازماً full-size frames کی صاف CFR sequence نہیں ہوتی۔ اس میں partial rectangles، blend/disposal rules، alpha، irregular delays، zero-duration frames اور ایسا timing metadata ہو سکتا ہے جسے generic tools misleading FPS میں summarize کر دیں۔

میری final pipeline اس طرح بنتی ہے:

animated WebP / GIF / APNG
        ↓
decode + reconstruct displayed canvas states
        ↓
recover source timeline
        ↓
select per-source CFR
        ↓
resize / pad without upscaling
        ↓
encode one H.264 segment
        ↓
repeat independently for every source
        ↓
concat with stream copy
        ↓
normalize final packet timeline
        ↓
ffprobe + packet checks + full decode
        ↓
atomic publish

Codec اس میں صرف ایک حصہ ہے۔ اصل مشکل representation بدلتے ہوئے animation کا مطلب برقرار رکھنا ہے۔

پہلا اصول: stored rectangle نہیں، displayed canvas extract کرو

سب سے خطرناک shortcut یہ فرض کرنا ہے کہ ہر stored frame پورا canvas replace کرتا ہے۔

Animated WebP میں frame position، rectangle size، blend mode اور disposal mode ہوتے ہیں۔ APNG میں offsets، dimensions، dispose اور blend operations ہیں۔ GIF بھی Graphic Control Extension کے ذریعے disposal behavior بیان کرتا ہے۔

اس لیے stored frame صرف ایک چھوٹا patch ہو سکتا ہے جو پچھلے canvas پر منحصر ہے۔ اسے full image سمجھ کر encode کریں تو چھوٹی animation نہیں بلکہ غلط animation ملتی ہے۔

میری extraction boundary displayed canvas state ہے: وہ مکمل composited تصویر جو درست decoder disposal اور blending کے بعد user کو دکھاتا۔

یہ correctness کی پہلی guarantee ہے؛ یہاں pixels غلط ہوں تو بعد کا H.264 encoder انہیں درست نہیں کر سکتا۔

Frame timing source data ہے، guessed FPS نہیں

  • Animated WebP ہر frame کا duration 1 ms units میں رکھتا ہے۔
  • GIF delay کو second کے hundredths میں رکھتا ہے۔
  • APNG delay numerator/denominator رکھتا ہے؛ denominator صفر ہو تو specification اسے 100 سمجھتی ہے۔

یہ delays ہی timeline ہیں۔ Average FPS صرف summary ہے۔

ایک حقیقی source 1264×720 تھا، اس میں 49 displayed frames تھے، delays 62 اور 63 ms کے درمیان بدل رہے تھے، اور total duration 3.063 s تھا۔ یہ تقریباً 16 fps cadence ہے کیونکہ 16 fps پر frame 62.5 ms ہوتا ہے۔

ایک probe نے اسی source کے لیے 25 fps رپورٹ کیا۔ اگر میں وہ عدد مان لیتا تو timing بدل جاتی یا غیر ضروری duplicate frames بنتے۔

CFR چننے سے پہلے خراب timing normalize کرتا ہوں

Zero اور بہت چھوٹے delays implementation-dependent playback پیدا کر سکتے ہیں۔ اس لیے explicit policy ضروری ہے۔ Timeline milliseconds میں رہتی ہے، minimum frame delay 10 ms ہے، اور useful timing نہ ملے تو 100 ms fallback استعمال ہوتا ہے۔

یہ universal numbers نہیں؛ اصل اصول یہ ہے کہ valid source timing محفوظ رہے اور normalization frame-rate selection سے پہلے ہو۔

ہر animation کو 30 fps کیوں نہیں بناتا

12 یا 16 بار فی سیکنڈ بدلنے والی animation کو 30 fps پر encode کرنا اکثر redundant temporal samples بناتا ہے۔ H.264 انہیں compress کر لے گا، مگر جو data درکار ہی نہیں اسے پیدا نہ کرنا بہتر ہے۔

ہر source کے لیے CFR الگ سے ان candidates سے چنتا ہوں:

10, 12, 15, 16, 18, 20, 24, 25, 30 fps

Selector سب سے کم candidate لیتا ہے جو source timeline کو صاف represent کرے؛ ورنہ fallback 30 fps۔ 62/63 ms example میں 16 fps فطری fit ہے۔

Final MP4 کے لیے ایک global CFR ضروری نہیں

ہر animation الگ segment ہے۔ ایک 12 fps، دوسرا 16، تیسرا 24 یا 30 ہو سکتا ہے۔ ہر segment اندر سے CFR ہے، مگر full output میں segment boundaries پر packet duration بدل سکتا ہے۔ یہ intentional ہے۔

90,000 Hz time base کیوں

FPSticks/frame
109000
127500
156000
165625
185000
204500
243750
253600
303000

تمام allowed FPS 90,000 کو exact integer ticks/frame میں divide کرتے ہیں، اس لیے concat اور validation کے لیے صاف grid ملتا ہے۔

Geometry: downscale، کبھی upscale نہیں

  • landscape: زیادہ سے زیادہ 1280×720
  • portrait: زیادہ سے زیادہ 720×1280
  • mixed/square: تقریباً 960×960

چھوٹا source چھوٹا ہی رہتا ہے۔ 900×600 کو 1280×720 کرنے سے detail نہیں بڑھتی، صرف pixels بڑھتے ہیں۔ Aspect ratio stretch نہیں کرتا؛ proportional scaling اور padding استعمال کرتا ہوں۔ Alpha کو H.264/yuv420p کے لیے background پر flatten کرتا ہوں۔

اس workload میں H.264 MP4 اکثر بہت چھوٹا کیوں ہوتا ہے

GIF، APNG اور Animated WebP خود بھی partial updates استعمال کر سکتے ہیں، اس لیے MP4 ہمیشہ چھوٹا ہونے کا دعویٰ غلط ہوگا۔

لیکن H.264 temporal redundancy کے لیے بنایا گیا ہے۔ Mostly-static illustrated scene میں inter prediction، reference pictures اور P/B frames بہت مؤثر ہوتے ہیں۔

Apple static web video کے لیے H.264 MP4 recommend کرتا ہے اور animated GIF کی جگہ MP4 استعمال کرنے کی ہدایت دیتا ہے۔ Safari documentation میں GIF کے لیے modern video codec کے مقابلے میں up to 12× bandwidth اور تقریباً 2× energy کی مثال دی گئی ہے۔ یہ 12× میرا benchmark نہیں، Apple کی مثال ہے۔

Lossy Animated WebP سے H.264 دوسری lossy generation ہے، اس لیے quality validation ضروری ہے۔

H.264 profile جان بوجھ کر conservative ہے

SettingValue
ContainerMP4
CodecH.264 / libx264
Sample entryavc1
Profile / levelMain @ 3.1
Pixel format8-bit yuv420p
Rate controlCRF 28
Presetveryslow
Tuneanimation
Reference frames4
Maximum B-frames5
Open GOPoff
B-pyramidnormal
Maximum GOPتقریباً 5 s
VBV4M maxrate / 8M bufsize
ColorBT.709, limited range
Track/movie timescale90,000
Audio/subtitles/datanone

Main@3.1 modern devices کی maximum capability نہیں؛ یہ میری conservative one-file baseline ہے۔ Android 6.0 سے Main Profile decoder required ہے اور 1280×720@30 fps کو H.264 HD recommendation کے طور پر دکھاتا ہے۔

CPU ایک بار خرچ، bandwidth بار بار بچت

-preset veryslow

Offline encoding ایک بار، delivery کئی بار ہوتی ہے۔ اس لیے x264 کو زیادہ CPU دے کر بہتر representation تلاش کروانا میرے workload میں منطقی ہے۔ Decoder complexity profile/level/refs/B-frames سے الگ محدود ہے۔ CRF 28 بھی illustrated bandwidth-first content کے لیے baseline ہے، universal number نہیں۔

ہر animation ایک independent H.264 segment ہے

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

اس سے پہلے timeline-aware frames اور geometry تیار ہوتے ہیں۔ GOP تقریباً selectedFps * 5 ہے۔

Concat کے لیے سخت segment contract ضروری ہے

FFmpeg concat demuxer compatible streams چاہتا ہے اور ہر file کی duration سے اگلے timestamps بناتا ہے، اس لیے incompatibility concat کے وقت نہیں بلکہ segment validation میں روکی جاتی ہے۔

ffmpeg -f concat -safe 0 -i segments.ffconcat \
  -c:v copy -an -sn -dn -movflags +faststart final.mp4

-c:v copy دوسرے lossy H.264 encode کو روکتا ہے۔

Stream copy timing correctness کی guarantee نہیں

90000 / 24 = 3750 ticks

ایک real output میں validator نے 3750 کے بجائے 3751-tick packet پکڑا۔ Visual playback ٹھیک نظر آ سکتا تھا، مگر exact grid invariant ٹوٹ چکا تھا۔

Final concat اب setts bitstream filter سے PTS/DTS/duration کو 90-kHz grid پر normalize کرتا ہے جبکہ -c:v copy برقرار رہتا ہے۔ Exact expression known segment timeline سے بنتی ہے؛ universal recipe نہیں۔

Validation encoder کا حصہ ہے

FFmpeg exit code 0 کافی نہیں۔ Streams، profile/level، pixel format، dimensions، time base، frame/packet counts، exact durations، PTS/DTS اور final duration check کرتا ہوں، پھر پورا file decode کرتا ہوں۔

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 -

تمام checks پاس ہونے کے بعد atomic publish ہوتا ہے۔

یہ pipeline کن غلطیوں کو روکتی ہے

  • raw subframe کو visible canvas سمجھنا
  • guessed FPS پر اعتماد
  • ہر source کو 30 fps بنانا
  • چھوٹے source کا upscale
  • aspect ratio stretch
  • concat کے بعد دوسری lossy encode
  • -c copy کو timing proof سمجھنا
  • full decode کے بغیر صرف header check

جو چیزیں جان بوجھ کر preserve نہیں کرتا

Alpha flatten ہوتا ہے، arbitrary VFR source-specific CFR approximation بنتا ہے، بہت بڑی resolution downscale ہوتی ہے، lossy WebP ایک extra lossy generation لیتا ہے، اور audio اس scope میں نہیں۔ یہ explicit trade-offs ہیں۔

کب یہ طریقہ استعمال نہیں کروں گا

Transparency لازمی ہو، exact irregular timing معنی رکھتا ہو، archival master چاہیے ہو، یا already adaptive multi-codec stack ہو تو اس pipeline کو سیدھا استعمال نہیں کروں گا۔ Already tiny optimized Animated WebP کے لیے پہلے benchmark کروں گا۔

موجودہ practical pipeline

  1. Real animation metadata پڑھنا۔
  2. Blend/disposal سے visible canvas states rebuild کرنا۔
  3. Delay normalize کر کے ms timeline بنانا۔
  4. 10/12/15/16/18/20/24/25/30 سے per-source CFR چننا۔
  5. No upscale، proportional resize/pad، alpha flatten۔
  6. H.264 Main@3.1/yuv420p/avc1، veryslow، CRF 28۔
  7. 90-kHz time base۔
  8. ہر segment validate کرنا۔
  9. -c:v copy concat۔
  10. Final timeline exact grid پر normalize کرنا۔
  11. Packet inspection + full decode۔
  12. پھر publish۔

اصل سبق: asset دراصل timeline ہے

Animated WebP، GIF اور APNG صرف “بہت سی تصاویر” نہیں؛ یہ وقت کے ساتھ بدلنے والے canvas states ہیں۔

Timeline کو source of truth سمجھنے کے بعد باقی فیصلے واضح ہو جاتے ہیں: fake motion کے بغیر cadence کم کیا جا سکتا ہے، fake pixels کے بغیر resolution کم، ہر source کا اپنا CFR، اور concat کے لیے دوسری lossy encode کی ضرورت نہیں۔

H.264 temporal redundancy کو استعمال کرتا ہے اور MP4 delivery آسان کرتا ہے، لیکن extraction یا timing غلط ہو تو codec کچھ نہیں بچا سکتا۔

اہم primary documentation