Ada satu asumsi yang saya bawa terlalu lama: kalau format barunya lebih efisien, hasilnya pasti lebih kecil. VP9 memang lebih efisien dari H.264. Tapi efisiensi codec tidak menjanjikan apa-apa soal ukuran file kalau kamu tidak memberi tahu encoder-nya seberapa boros dia boleh bekerja.
Saya kena batunya waktu merapikan pipeline media di situs portofolio saya sendiri. Video demo proyek masih tersimpan sebagai mp4, dan saya mau memindahkannya ke webm VP9 supaya halaman proyek lebih ringan. Perintahnya sederhana, tinggal jalan sekali, selesai. Begitu pikir saya.
Gejalanya: file webm keluar lebih gemuk dari mp4 sumbernya
Saya pakai angka yang paling sering muncul kalau kamu mencari rekomendasi VP9 untuk web:
ffmpeg -y -i in.mp4 -an \
-c:v libvpx-vp9 -crf 33 -b:v 0 \
-row-mt 1 -cpu-used 4 -pix_fmt yuv420p \
out.webmEncode-nya sukses. Tidak ada warning yang mencurigakan. Tapi begitu saya bandingkan ukuran input dan output, sebagian webm justru lebih besar dari mp4 yang jadi sumbernya. Bukan selisih tipis yang bisa diabaikan, tapi cukup besar sampai seluruh alasan saya melakukan konversi ini jadi hilang.
Reaksi pertama saya salah arah. Saya sempat curiga flag -b:v 0 yang bikin bitrate lepas kendali, lalu curiga -cpu-used 4 yang mengorbankan kompresi demi kecepatan. Dua-duanya bukan penyebabnya.
Akar masalahnya: CRF menjawab pertanyaan yang salah
-crf di libvpx-vp9 adalah target kualitas konstan, bukan target ukuran. Dipasangkan dengan -b:v 0, artinya kamu benar-benar mematikan batas bitrate dan berkata ke encoder: kejar kualitas segini, pakai bit sebanyak yang kamu butuhkan.
Di situlah masalahnya muncul. Video sumber saya sudah terkompresi habis dari langkah sebelumnya. Detail halusnya sudah lama hilang, dan yang tersisa sebagian adalah artefak kompresi: blok, banding, dan noise buatan yang bentuknya justru sulit ditebak encoder.
VP9 tidak tahu mana yang detail asli dan mana yang bekas luka kompresi. Baginya semua itu isi frame yang harus direproduksi seakurat mungkin sampai target crf 33 tercapai. Jadi encoder-nya rajin, dan hasilnya dia menghabiskan lebih banyak bit daripada yang dipakai sumbernya sendiri.
Jadi crf 33 itu bukan angka yang salah. Dia cuma kelewat murah hati untuk sumber yang kualitasnya sudah tinggal segitu. Kesalahan saya adalah memperlakukan CRF sebagai satu konstanta global, padahal dia harus disetel relatif terhadap kualitas sumbernya.
Perbaikannya: dua angka, bukan satu
Begitu cara pandangnya benar, solusinya jadi membosankan. Saya pisahkan video jadi dua kelompok.
Untuk klip baru yang berkualitas tinggi dan belum pernah dikompresi ulang, crf 33 tetap dipakai:
ffmpeg -y -i in.mp4 -an \
-c:v libvpx-vp9 -crf 33 -b:v 0 \
-row-mt 1 -cpu-used 4 -pix_fmt yuv420p \
out.webmUntuk video lama yang sudah terkompresi, angkanya saya naikkan ke 38:
ffmpeg -y -i in.mp4 -an \
-c:v libvpx-vp9 -crf 38 -b:v 0 \
-row-mt 1 -cpu-used 4 -pix_fmt yuv420p \
out.webmHasilnya langsung masuk akal. Set video lama turun dari 107 MB ke 76 MB, dan setiap file akhirnya lebih kecil dari sumbernya. Klip baru yang tadinya 17 sampai 20 MB per file jatuh ke sekitar 2 MB di crf 33, karena di situ memang ada banyak kualitas berlebih yang layak dibuang.
Perhatikan bahwa yang berubah cuma satu angka. Sisanya tetap:
-anmembuang trek audio. Video demo di halaman proyek tidak pernah diputar dengan suara, jadi audionya murni beban.- Resolusi native dan 60fps saya pertahankan. Saya tidak menurunkan resolusi atau frame rate untuk mengejar ukuran, karena yang mahal di sini bukan dimensinya, tapi seberapa keras encoder disuruh mengejar kualitas.
-row-mt 1dan-cpu-used 4mengatur kecepatan encode.-cpu-usedmemang menukar sedikit efisiensi kompresi demi kecepatan, tapi dia tidak pernah menggeser target kualitas yang dikejar CRF.-pix_fmt yuv420psupaya hasilnya aman diputar di mana saja.
Thumbnail-nya ikut sekalian
Karena sedang membereskan media, thumbnail statisnya saya konversi ke WebP dengan batas lebar yang masuk akal:
ffmpeg -i in.png -vf "scale='min(1600,iw)':-2" -c:v libwebp -q:v 82 out.webpmin(1600,iw) mencegah gambar kecil di-upscale, dan -2 menjaga tinggi hasilnya tetap genap supaya encoder tidak rewel.
Cek cepat sebelum commit
Pelajaran pahitnya: saya tidak pernah membandingkan ukuran input dan output sampai semuanya selesai dikonversi. Sekarang perbandingan itu jadi bagian dari prosesnya, bukan kejutan di akhir:
for f in *.mp4; do
printf "%s %s -> %s\n" "$f" \
"$(du -h "$f" | cut -f1)" \
"$(du -h "${f%.mp4}.webm" | cut -f1)"
doneKalau ada baris yang panahnya menunjuk ke angka lebih besar, naikkan CRF untuk file itu dan encode ulang. Tidak perlu sistem yang canggih, cukup satu tabel yang jujur.
Yang saya bawa pulang
- CRF adalah target kualitas, bukan target ukuran. Dengan
-b:v 0, encoder boleh menghabiskan bit sebanyak apa pun untuk mencapainya. - Sumber yang sudah terkompresi berat itu bukan sumber berkualitas rendah di mata encoder. Artefak kompresi tetap dianggap detail yang harus direproduksi, dan itu mahal.
- Tidak ada satu angka CRF yang benar untuk semua video. Setel per kualitas sumber, bukan per proyek.
- Kalau tujuan konversimu adalah ukuran, ukur ukurannya. Encode yang sukses bukan berarti encode yang berguna.