Mengapa Pratinjau Video Anda Berbohong tentang Ekspor
Editor video Anda terlihat selesai. Pratinjau jernih, teks ditempatkan tepat di mana pengguna menyeretnya, dan _timeline_ bergerak dengan mulus. Kemudian pengguna menekan Export, dan teks kembali di tempat yang salah, dengan ukuran yang salah, terkadang terpotong seluruhnya dari tepi. Tidak ada yang _crash_, tidak ada error di _log_ — pratinjau dan _file_ hanya tidak sesuai.
Ini adalah _bug_ paling umum di setiap editor video, dan ini adalah masalah _data modeling_ daripada masalah _rendering_. Di bawah ini adalah satu kebiasaan yang membuat _bug_ ini tidak mungkin terjadi, cara mengatur ukuran _frame_ dengan benar dari rasio aspek, dan cara menjaga penyeretan tetap cepat di ponsel murah. Ide-ide ini berlaku dalam bahasa atau _UI framework_ apa pun.
Rasio Aspek Adalah Bentuk, Resolusi Adalah Ukuran
Kebanyakan _bug frame_ dimulai di sini. Rasio aspek menjelaskan bentuk _frame_ dan tidak ada yang lain: 9:16 adalah tinggi, 16:9 adalah lebar. Resolusi menjelaskan ukuran dalam _pixel_, seperti 1080x1920. Satu bentuk mendukung banyak ukuran, jadi kedua nilai tersebut tidak pernah dapat dipertukarkan.
| Rasio Aspek | Nilai Numerik | Penggunaan Umum |
| 16:9 | 1.78 | YouTube, web lanskap |
| 9:16 | 0.56 | Reels, TikTok, Shorts |
| 1:1 | 1.00 | Postingan _feed_ kotak |
| 4:5 | 0.80 | Postingan _feed_ potret |
Simpan rasio sebagai teks biasa dan ubah menjadi angka hanya saat Anda melakukan perhitungan. Simpan tinggi target untuk ekspor, lalu turunkan lebar dari rasio, dan paksa kedua angka menjadi genap sebelum mencapai _encoder_. Dimensi ganjil adalah penyebab diam dari sejumlah ekspor yang gagal secara mengejutkan.
height = width / ratioToNumber(ratio) // mengatur ukuran kotak pratinjau
function widthFromHeight(ratio, height): raw = height * ratioToNumber(ratio) return makeEven(round(raw)) // encoder membutuhkan sisi genap
// 9:16 at 1080 tall -> 608 x 1080// 16:9 at 1080 tall -> 1920 x 1080
Simpan Posisi sebagai Pecahan, Jangan Pernah _Pixel_
Editor hidup dalam tiga dunia dengan tiga ukuran berbeda: dokumen yang disimpan, pratinjau di layar, dan _file_ yang diekspor. Dokumen adalah satu-satunya sumber kebenaran — dua lainnya hanyalah _rendering_ dari dokumen tersebut dalam skala yang berbeda.
Proyek -> Klip (sumber, potong, filter) -> Overlay (teks / stiker)
Dokumen Renderer Pratinjau Renderer Ekspor x = 0.5, y = 0.9 --> x * 360 px --> x * 1080 px --> MP4 | ^ ^ +------------------------+------------------------+ satu pecahan tersimpan, satu formula, dua ukuran
Jika Anda menyimpan 540 _pixel_, angka tersebut hanya benar di perangkat tempat pengukuran dilakukan. Jika Anda menyimpan 0.5, itu berarti "pusat horizontal" di setiap ukuran selamanya. Setiap posisi, _offset_ dan skala dalam model harus berupa pecahan antara 0 dan 1.
type Overlay { x: number // 0.0-1.0 melintasi (0.5 = tengah) y: number // 0.0-1.0 ke bawah (0.9 = dekat bawah) scale: number // 1.0 = ukuran normal rotation: number // derajat startTimeMs: number // kapan muncul endTimeMs: number // kapan menghilang}
Ekspor kemudian menjadi hampir membosankan, itulah intinya. _Renderer_ menggunakan formula yang sama dengan pratinjau dengan pengganda yang lebih besar: px = overlay.x * exportWidth. FFmpeg menangani _frame_ itu sendiri, dan ekspresi pemusatannya (ow-iw)/2 adalah aritmetika yang sama yang digunakan pratinjau untuk _letterbox_. Karena nilai yang tersimpan tidak pernah berubah, teks mendarat tepat di tempat yang benar.
ffmpeg -i input.mp4 \ -vf "scale=1080:1920:force_original_aspect_ratio=decrease,\ pad=1080:1920:(ow-iw)/2:(oh-ih)/2:0xD3D3D3" \ -c:v libx264 -crf 23 -pix_fmt yuv420p -c:a aac output.mp4
Gambar Pratinjau dalam Satu _Pass_
Ukur kotak pratinjau saat _runtime_ alih-alih melakukan _hard-coding_, karena ponsel, tablet, dan jendela _desktop_ yang diubah ukurannya semuanya akan memberikan ukuran yang berbeda. Gambar setiap _overlay_ dalam satu _pass_ _canvas_ daripada memasang masing-masing sebagai elemen _UI_ terpisah — satu _pass_ akan tetap mulus saat jari bergerak.
Dua aturan menjaga _loop_ tetap jujur. Lewati _overlay_ apa pun di luar rentang waktunya, dan selalu pasangkan save() dengan restore() agar satu item tidak bocor transformasinya ke item berikutnya.
function drawPreview(canvas, overlays, currentTime, box): for each overlay in overlays: if currentTime < overlay.startTimeMs: skip if currentTime > overlay.endTimeMs: skip
px = overlay.x * box.width // formula kunci py = overlay.y * box.height
canvas.save() canvas.move(px, py) canvas.rotate(overlay.rotation) canvas.resize(overlay.scale) canvas.drawText(overlay.content) canvas.restore() // tidak pernah opsional
Jaga Penyeretan Tetap Halus dengan _State_ Dua Tingkat
Sebuah jari yang menyeret melaporkan sekitar enam puluh posisi per detik. Jika setiap posisi menulis ke _project store_ Anda, Anda akan membayar untuk validasi, persistensi, dan _rebuild state_ penuh enam puluh kali per detik, dan penyeretan akan terlihat tersendat-sendat. Bagi pekerjaan menjadi dua tingkat sebagai gantinya:
- Tingkat 1, fana: sebuah _map_ kecil
liveDragyang menyimpan posisi jari saat ini. Perbarui di setiap gerakan dan gambar ulang _canvas_. Tidak ada yang lain yang berjalan. - Tingkat 2, tahan lama: pada akhir penyeretan, _commit_ pecahan akhir ke _overlay_ dalam model proyek, bersihkan entri
liveDrag, dan catat satu langkah _undo_.
Manfaatnya melampaui _frame rate_. Riwayat _undo_ tetap berguna karena satu penyeretan menghasilkan satu entri daripada ratusan, dan _autosave_ berhenti membebani _disk_. Inilah cara Figma dan Canva menjaga manipulasi langsung tetap responsif.
Konteks Dunia Nyata
Di MicrocosmWorks, kami menangani editor video pendek yang teksnya bergeser saat diekspor. Tim telah menyimpan posisi _overlay_ sebagai _pixel_ perangkat yang dibaca langsung dari _canvas_ pratinjau, sehingga teks yang dibuat di ponsel kecil muncul tinggi dan kecil di _file_ 1080p, dan mengubah rasio aspek mendorong beberapa _overlay_ keluar dari _frame_ sepenuhnya.
Kami memigrasikan model ke koordinat 0-ke-1 yang dinormalisasi, membuat kedua _renderer_ berbagi _helper_ _fraction-times-size_ tunggal, dan menerapkan dimensi keluaran genap yang berasal dari rasio yang tersimpan. Pratinjau dan ekspor cocok di setiap perangkat uji. Memindahkan _drag commits_ ke _drag-end_ menghilangkan _lag_ yang dilaporkan pengguna pada perangkat keras Android kelas bawah. Anda dapat melihat jenis video pendek dan pekerjaan pengeditan yang dihasilkan dari ini di portofolio proyek kami.
Kesimpulan
Pratinjau dan ekspor adalah dua _rendering_ dari satu dokumen, jadi berikan mereka satu sumber kebenaran. Simpan posisi sebagai pecahan, gunakan formula _fraction-times-size_ yang sama di kedua _renderer_, turunkan lebar dari rasio aspek, jaga dimensi tetap genap, dan _commit_ perubahan penyeretan hanya saat jari terangkat. Lima kebiasaan tersebut menghilangkan seluruh kategori _bug_ sebelum ditulis.
Membangun _media engine_ di mana tampilan interaktif dan _file_ akhir sesuai adalah fokus utama pekerjaan rekayasa video dan _streaming_ kami di MicrocosmWorks.
Mengeluarkan editor video atau konten dan ingin pratinjau dan ekspor benar-benar sesuai? Kami telah memecahkan kelas _bug_ ini di alat pengeditan video pendek. Bicaralah dengan tim _engineering_ kami →
Baca lebih lanjut dari tim kami
1. Mengoptimalkan Logo Saluran untuk Resolusi Video yang Berbeda
2. Jepret Piring, Catat Makanan: _Pipeline_ Nutrisi _Computer-Vision_
3. Mengoptimalkan Logo Saluran untuk Resolusi Video yang Berbeda

