Dari laptop 4 GB ke MP4 9:16, tanpa tagihan API.
Blueprint aslinya menganggap ada GPU 12 GB. Kamu tidak punya itu — dan ternyata tidak butuh. Runbook ini menukar satu asumsi mahal (gambar dibuat AI) dengan satu keputusan gratis (gambar digambar sebagai kode), lalu menyusun ulang seluruh pipeline supaya muat di mesinmu. Tujuh tahap, masing-masing berhenti di sesuatu yang bisa kamu tonton.
Jangan generate gambar di laptop ini
Flux dan SDXL butuh 8–12 GB VRAM. Untuk video infografik seperti VID-002, SVG di dalam Remotion lebih tajam, konsisten, dan gratis.
Claude Max & Codex = tim developer
Langganan itu untuk pemakaian interaktif olehmu saat membangun sistem. Untuk generate naskah saat produksi, pakai Gemini free tier atau batch manual.
Satu video utuh dulu, baru otomatis
Selesaikan Tahap 1–3 secara manual sampai ada MP4 bersuara. Otomasi yang dibangun sebelum itu selalu mengotomatiskan hal yang salah.
Siapkan mesin
Selesai kalau: node -v, python --version, dan ffmpeg -version semuanya menjawab tanpa error, dan foldernya sudah berdiri.
Tiga runtime saja. Node untuk Remotion, Python untuk suara dan subtitle, FFmpeg untuk menyatukan semuanya. Tidak ada CUDA, tidak ada Docker — di laptop 4 GB, Docker justru merebut RAM yang kamu butuhkan untuk render.
# Wajib ada ketiganya. Kalau salah satu kosong, pasang dulu. node -v # butuh v20 atau lebih baru python3 -V # butuh 3.10+ ffmpeg -version | head -1
FFmpeg di Windows paling gampang lewat winget install Gyan.FFmpeg, di macOS brew install ffmpeg, di Ubuntu sudo apt install ffmpeg.
Struktur folder
Satu folder induk, dua sub-proyek yang sengaja dipisah: bagian React dan bagian Python tidak perlu tahu satu sama lain, mereka hanya bertukar file.
Karena retry yang benar butuh tahu apa yang sudah jadi. Kalau TTS gagal, worker cukup melihat voice/ masih kosong sementara recipe.json sudah ada — jadi dia mengulang TTS saja, bukan seluruh pipeline. Prinsip ini yang bikin sistemnya tahan banting, dan tidak perlu database untuk mewujudkannya.
Render scene pertama
Selesai kalau: ada file MP4 7 detik berisi sayap dan garis aliran udara yang bergerak, dan kamu sudah menontonnya di HP.
Ini tahap paling penting dan paling sering dilewati orang. Sebelum membangun apa pun, buktikan dulu mesinmu sanggup mengeluarkan video. Kalau tahap ini lancar, sisanya cuma soal ketekunan.
npx create-video@latest --yes --blank --no-tailwind studio cd studio npm install
Lalu timpa isinya dengan file dari paket vid002-lift: src/scenes/Lift.tsx, src/Root.tsx, src/data/recipe.json, dan remotion.config.ts.
npx remotion studio # preview interaktif, geser timeline npx remotion still Lift out/cek.png --frame=120 # cek 1 frame, ±10 detik npx remotion render Lift out/lift.mp4 # render penuh
Selalu cek dengan remotion still dulu sebelum render penuh. Satu frame keluar dalam hitungan detik; salah posisi teks jadi ketahuan sebelum kamu menunggu lima menit sia-sia.
Kalau render berat atau mati sendiri
- Turunkan
Config.setConcurrency(2)jadi1diremotion.config.ts. Tiap unit concurrency ≈ satu tab Chrome headless. - Tutup browser dan Slack saat render. Di laptop 4 GB, ini bukan saran sopan-santun — itu memang penentu berhasil atau tidak.
- Kalau muncul error terkait GPU/WebGL, tambahkan
--gl=anglepada perintah render.
Menambah 4 scene sisanya
Di sinilah Claude Code kamu bekerja. Buka terminal di folder studio/ dan minta scene berikutnya dengan referensi konkret — bukan deskripsi abstrak.
claude -p "Baca src/scenes/Lift.tsx. Buat src/scenes/Thrust.tsx
dengan pola koreografi yang sama: mesin jet SVG, api yang
berdenyut, dan panah dorong ke kanan. Ambil durasi dan narasi
dari scene sequence 4 di src/data/recipe.json. Daftarkan
komposisinya di Root.tsx."
Remotion punya skill resmi untuk coding agent, dan ini benar-benar menaikkan kualitas hasilnya:
npx -y skills@latest add remotion-dev/skills -g -y
Suara — Piper TTS lokal
Selesai kalau: ada voice.wav berbahasa Indonesia yang enak didengar, dibuat sepenuhnya offline.
Piper adalah pilihan yang tepat justru karena keterbatasan mesinmu: model VITS yang diekspor ke ONNX, jalan di CPU, dan satu suara cuma puluhan megabyte. Di CPU desktop modern kecepatannya sekitar 10× realtime — narasi 30 detik selesai dalam hitungan detik.
cd worker
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install piper-tts
Suara Indonesia yang tersedia resmi adalah id_ID-news_tts-medium — suara pembaca berita, kualitas 22 kHz. Cocok untuk gaya edukasi singkat.
python3 -m piper.download_voices id_ID-news_tts-medium
echo 'Kenapa pesawat bisa terbang?' \
| piper -m id_ID-news_tts-medium -f uji.wav
Kalau suaranya terasa terlalu cepat atau datar untuk gaya kontenmu, atur --length-scale (angka lebih besar = lebih lambat) dan --sentence-silence untuk jeda antar kalimat. Ini knob yang paling terasa efeknya pada retensi penonton.
Per-scene, bukan sekali panjang
Ini keputusan struktural yang penting. Rekam satu file per scene, bukan satu file 30 detik. Alasannya: kalau kalimat scene 4 perlu direvisi, kamu cukup merekam ulang scene 4. Dan Remotion bisa menaruh tiap audio tepat di <Sequence>-nya masing-masing, jadi sinkronisasi otomatis benar.
import json, wave, pathlib from piper import PiperVoice VOICE = PiperVoice.load("id_ID-news_tts-medium.onnx") def bikin_suara(video_dir: str): root = pathlib.Path(video_dir) recipe = json.loads((root / "recipe.json").read_text()) out = root / "voice"; out.mkdir(exist_ok=True) for scene in recipe["scenes"]: target = out / f"scene-{scene['sequence']:02d}.wav" if target.exists(): # sudah ada = lewati, ini inti retry continue with wave.open(str(target), "wb") as f: VOICE.synthesize_wav(scene["narration"], f) print("ok", target.name)
Repo Piper yang aktif sekarang (OHF-Voice/piper1-gpl) berlisensi GPL-3.0, bukan MIT seperti repo lama yang sudah diarsipkan. Untuk pemakaian sebagai alat terpisah yang dipanggil lewat CLI ini umumnya tidak jadi masalah, tapi kalau nanti kamu berencana menjual sistemnya, baca dulu ketentuannya.
Subtitle — Whisper lokal
Selesai kalau: ada captions.json berisi timing per kata, dan caption di video sudah menempel pas dengan suaranya.
Logikanya mungkin terasa terbalik: kita sudah punya teks narasinya, kenapa masih perlu speech-to-text? Karena yang kita cari bukan teksnya — tapi kapan tiap kata diucapkan. Itulah yang membuat caption meletup per kata seperti di Shorts, dan retensi penonton naik karenanya.
Jalur A — whisper.cpp lewat Remotion (disarankan)
Paling pas untuk mesinmu: whisper.cpp dioptimalkan untuk CPU, dan hasilnya langsung berbentuk objek Caption yang dimengerti Remotion. Satu skrip Node, tanpa Python sama sekali.
npm i --save-exact @remotion/install-whisper-cpp @remotion/captions
import path from 'path'; import fs from 'fs'; import {execSync} from 'child_process'; import { installWhisperCpp, downloadWhisperModel, transcribe, toCaptions, } from '@remotion/install-whisper-cpp'; const to = path.join(process.cwd(), 'whisper.cpp'); await installWhisperCpp({to, version: '1.5.5'}); await downloadWhisperModel({model: 'small', folder: to}); // 'small' (bukan small.en) — versi .en tidak bisa bahasa Indonesia const wav = process.argv[2]; const wav16 = wav.replace('.wav', '-16k.wav'); execSync(`ffmpeg -i ${wav} -ar 16000 -ac 1 ${wav16} -y`); // whisper.cpp hanya menerima WAV 16-bit 16 kHz mono const hasil = await transcribe({ inputPath: wav16, whisperPath: to, whisperCppVersion: '1.5.5', model: 'small', language: 'id', tokenLevelTimestamps: true, // ini yang bikin timing per kata akurat }); const {captions} = toCaptions({whisperCppOutput: hasil}); fs.writeFileSync('public/captions.json', JSON.stringify(captions, null, 2));
Di dalam scene, kelompokkan kata jadi "halaman" caption dengan createTikTokStyleCaptions() dari @remotion/captions — itu yang menghasilkan ritme baca khas Shorts tanpa kamu menghitung frame manual.
Jalur B — faster-whisper (Python)
Pakai ini kalau kamu lebih nyaman di Python atau ingin transkripnya dipakai di luar Remotion juga.
# pip install faster-whisper from faster_whisper import WhisperModel model = WhisperModel("small", device="cpu", compute_type="int8") # int8 di CPU: ±4x lebih cepat daripada fp16, RAM jauh lebih hemat segments, info = model.transcribe( "voice/scene-03.wav", language="id", word_timestamps=True, vad_filter=True, ) kata = [ {"text": w.word, "startMs": round(w.start * 1000), "endMs": round(w.end * 1000)} for seg in segments for w in seg.words ]
Di CPU, large-v3 butuh sekitar 5–10× durasi audionya dan RAM yang tidak kamu punya. Untuk narasi bersih hasil TTS — yang pengucapannya sempurna dan tanpa noise — model small sudah lebih dari cukup, bahkan base sering lolos.
Naskah & resep JSON
Selesai kalau: kamu bisa menghasilkan recipe.json yang valid untuk topik baru, dan ada gerbang persetujuan sebelum resep itu dipakai.
Resep adalah kontrak antara bagian AI dan bagian render. Selama bentuknya benar, sumbernya tidak penting — mau dari Gemini, dari Claude, atau kamu ketik sendiri. Karena itu jangan mulai dari "LLM mana yang dipakai", tapi dari "seperti apa bentuk JSON-nya".
Aturan yang harus dijaga
- Setiap scene wajib punya
componentyang sudah ada file .tsx-nya. Kalau LLM mengarangcomponent: "Explosion"sementara komponennya tidak ada, render gagal. Kirimkan daftar komponen yang tersedia di dalam prompt. - Total
durationsemua scene harus sama dengandurationvideo. Validasi ini menangkap 90% resep rusak. - Narasi ditulis untuk didengar, bukan dibaca. Kalimat pendek, tanpa tanda kurung, angka ditulis sebagaimana diucapkan.
KOMPONEN = {"Hook", "Intro", "Lift", "Thrust", "DragWeight"}
def periksa(r: dict) -> list[str]:
salah = []
total = sum(s["duration"] for s in r["scenes"])
if total != r["duration"]:
salah.append(f"total scene {total} dtk != durasi video {r['duration']} dtk")
for s in r["scenes"]:
if s["component"] not in KOMPONEN:
salah.append(f"komponen tak dikenal: {s['component']}")
if len(s["narration"]) > 220:
salah.append(f"scene {s['sequence']}: narasi kepanjangan")
return salah
Dua cara menghasilkan resep
| Cara | Kapan dipakai | Biaya |
|---|---|---|
| Gemini free tier saat runtime |
Kalau kamu ingin pipeline benar-benar otomatis dari topik ke video. Kuota gratisnya cukup untuk puluhan naskah sehari. | Rp 0 |
| Batch manual via Claude Code |
Minta 20 resep sekaligus dalam satu sesi, simpan sebagai file. Hasilnya lebih terkurasi dan kamu sempat menyaring topik lemah. Untuk volume awal, ini biasanya lebih baik. | sudah termasuk langganan |
Blueprint aslinya menaruh SCRIPT_REVIEW sebelum aset dibuat, dan itu keputusan yang benar. Menonton ulang video 30 detik yang hook-nya lemah jauh lebih mahal daripada membaca empat kalimat selama sepuluh detik. Biarkan pipeline berhenti di sana dan menunggu kamu.
Orkestrator
Selesai kalau: satu perintah python run.py VID-002 menjalankan semua tahap, dan menjalankannya ulang setelah gagal hanya mengerjakan yang belum selesai.
Blueprint menyebut PostgreSQL, Redis, dan MinIO. Untuk satu laptop dan satu operator, ketiganya adalah biaya RAM tanpa manfaat. Ganti dengan yang setara tapi gratis dan ringan: filesystem sebagai storage, SQLite sebagai database, dan status.json sebagai antrean.
Kunci desainnya bukan pada tool, tapi pada satu ide: setiap tahap harus bisa dijalankan ulang tanpa merusak apa pun. Kalau outputnya sudah ada, tahap itu melewati dirinya sendiri. Dengan begitu retry tidak perlu logika khusus — cukup jalankan ulang seluruhnya.
import sys, json, subprocess, pathlib TAHAP = [ ("RESEARCH", "research.json"), ("SCRIPT_DRAFT", "recipe.json"), ("SCRIPT_APPROVED", "approved.flag"), # disentuh manusia ("VOICE_GENERATION", "voice/scene-01.wav"), ("SUBTITLE", "captions/captions.json"), ("RENDERED", "out/final.mp4"), ] def jalankan(vid: str): root = pathlib.Path("../videos") / vid for nama, penanda in TAHAP: if (root / penanda).exists(): print(f"lewati {nama} — sudah ada") continue print(f"jalankan {nama}") try: subprocess.run(["python", f"steps/{nama.lower()}.py", vid], check=True) except subprocess.CalledProcessError: tulis_status(root, f"FAILED_{nama}") print(f"berhenti di {nama}. Perbaiki, lalu jalankan perintah yang sama.") return tulis_status(root, nama) print("selesai — MP4 siap direview") def tulis_status(root, status): (root / "status.json").write_text(json.dumps({"status": status})) if __name__ == "__main__": jalankan(sys.argv[1])
Saat kamu sudah punya lebih dari sekitar 50 video dan mulai ingin bertanya hal seperti "mana saja yang gagal minggu ini". Sebelum itu, status.json per folder sudah menjawab semua pertanyaan yang benar-benar kamu punya — dan bisa dibaca mata telanjang saat ada yang aneh.
Back-office yang sudah kita buat adalah tampilan dari data ini. Kalau nanti mau menyambungkannya, cukup sajikan folder videos/ sebagai JSON lewat satu endpoint kecil, dan ganti data dummy di halaman Dashboard dengan hasil fetch.
Rakit & terbit
Selesai kalau: video utuh 30 detik ada di YouTube Shorts, dan kamu tahu berapa banyak yang bisa diunggah per hari.
Remotion sudah menghasilkan MP4 lengkap dengan audio, jadi FFmpeg di sini bukan untuk menggabung — melainkan untuk memastikan hasil akhirnya benar-benar sesuai spesifikasi Shorts, dan untuk mengambil thumbnail.
# Pastikan 1080x1920, H.264, AAC, faststart untuk streaming ffmpeg -i out/final.mp4 \ -vf "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:-1:-1" \ -c:v libx264 -preset medium -crf 22 -pix_fmt yuv420p \ -c:a aac -b:a 128k -movflags +faststart \ out/VID-002-final.mp4 -y # Thumbnail dari detik ke-2 ffmpeg -i out/VID-002-final.mp4 -ss 2 -frames:v 1 out/thumb.jpg -y
YouTube Data API memberi kuota harian standar 10.000 unit per project, sementara satu upload video memakan sekitar 1.600 unit. Artinya sekitar enam video per hari — dan itu sudah termasuk operasi lain seperti update metadata. Rancang jadwal produksimu di angka itu, jangan di angka yang lebih besar.
Selama volumemu masih di bawah itu, mengunggah manual juga sepenuhnya masuk akal. Otomasi upload adalah hal terakhir yang perlu kamu bangun, bukan yang pertama.
Peta beban mesin
Semua angka di bawah ini asumsinya laptop CPU-only, RAM 16 GB, dan tidak ada aplikasi berat lain yang jalan.
| Tahap | Tool | VRAM | RAM | Waktu per video 30 dtk |
|---|---|---|---|---|
| Naskah | Gemini free / batch | 0 | — | ±20 detik |
| Visual | SVG di Remotion | 0 | — | 0 (sudah jadi kode) |
| Suara | Piper medium | 0 | ±300 MB | ±5 detik |
| Subtitle | whisper.cpp small | 0 | ±1,2 GB | 1–3 menit |
| Render | Remotion + Chrome | 0 | ±1,5–3 GB | 8–20 menit |
| Encode akhir | FFmpeg | 0 | ±200 MB | ±30 detik |
Totalnya: sekitar 10–25 menit per video, hampir seluruhnya terpakai untuk render. Artinya realistis 2–4 video sehari sambil laptopnya tetap kamu pakai bekerja, atau lebih banyak kalau dijalankan malam hari secara batch.
Kapan pantas mulai membayar
Urutan yang masuk akal kalau nanti ada anggaran, dari yang paling terasa dampaknya:
- VPS untuk render (~Rp 150–300rb/bulan). Ini yang pertama, bukan AI-nya. Laptopmu bebas dan render bisa jalan semalaman.
- TTS berbayar kalau suara Piper terasa membatasi setelah kamu punya penonton tetap. Kualitas suara berpengaruh besar pada retensi.
- AI image paling akhir, dan hanya untuk video yang memang butuh foto realistis. Video infografik tidak pernah membutuhkannya.
Checklist
Centang saat sebuah tahap benar-benar menghasilkan file yang bisa kamu buka — bukan saat kodenya selesai ditulis.
Masalah yang hampir pasti kamu temui
| Gejala | Penyebab biasanya | Tindakan |
|---|---|---|
| Render berhenti di tengah tanpa pesan jelas | RAM habis karena terlalu banyak tab Chrome headless | Turunkan setConcurrency ke 1, tutup aplikasi lain |
| Caption bergeser dari suaranya | Audio belum diresample ke 16 kHz mono sebelum masuk Whisper | Jalankan ffmpeg -ar 16000 -ac 1 dulu |
| Whisper menghasilkan teks bahasa Inggris | Model .en terpakai, atau parameter bahasa tidak diisi |
Pakai model small tanpa akhiran, set language: 'id' |
| Font di video berbeda dengan yang di preview | Font sistem tidak tersedia di lingkungan render | Pakai @remotion/google-fonts supaya font ikut dibundel |
| Render gagal karena file audio tidak ditemukan | <Audio> menunjuk file yang belum dibuat |
Biarkan blok audio tetap komentar sampai Tahap 2 selesai |
| Video jadi, tapi terasa membosankan | Animasi mengikuti dekorasi, bukan penjelasan | Untuk tiap gerakan, tanya: kalimat apa yang sedang dijelaskan? |
Angka waktu di runbook ini adalah perkiraan untuk laptop kelas menengah tanpa akselerasi GPU; mesinmu bisa berbeda cukup jauh. Yang tidak berubah adalah urutannya: setiap tahap berhenti di sesuatu yang bisa dibuka dan ditonton, supaya kamu selalu tahu persis di mana posisimu. Centangan di halaman ini tidak tersimpan setelah tab ditutup.