Vertical video: still-image Ken Burns and 9:16 blur-fit
Two ways to end up with a clean 1080×1920 clip: animate a still image with a slow push-in, or fit an already-landscape video into a vertical frame without black bars. Both commands below are lifted directly from the toolkit scripts that ship with the SOP Pack.
1. Still image → Ken Burns push-in
A single still, upscaled first so zoompan has real pixels to zoom into, then
cropped to a 9:16 working size and pushed in slowly over the clip's duration.
ffmpeg -hide_banner -y -loop 1 -i hero.png -t 5 \
-vf "scale=1620:2880:force_original_aspect_ratio=increase,crop=1620:2880,zoompan=z='min(zoom+0.0012,1.15)':d=150:x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':s=1080x1920:fps=30,setsar=1" \
-c:v libx264 -pix_fmt yuv420p -r 30 hero_5s.mp4
Why d is seconds × fps, not 1, here
zoompan's d is output frames per input frame, not a total frame
count. The common shortcut — force the source to the target fps and set d=1,
letting the zoom ramp ride the running frame index — only works when the input is already a
multi-frame video at that fps. A still loaded with -loop 1 is a single input
frame with no running index to drive the ramp, so d=1 just re-emits that one
frame forever with the zoom frozen. Set d = seconds * fps (5s × 30fps = 150 in
the command above) so zoompan advances its own internal counter across the
output instead. This is the exact pitfall the SOP Pack's worked example (§9.5) walks through
with the same numbers.
-pix_fmt yuv420p keeps the output playable everywhere (QuickTime, most phones,
every platform upload path) — without it, some libx264 outputs default to a pixel format
that only decodes correctly in ffmpeg-based players. -r 30 pins the frame rate
so the zoompan output and the container agree.
2. Landscape footage → 9:16 with a blurred fill
When you don't want black bars around a 16:9 source, split the stream into a blurred, cropped background and a scaled foreground, then overlay the foreground centered on top.
ffmpeg -hide_banner -y -i landscape.mp4 \
-vf "split[a][b];[a]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,boxblur=30[bg];[b]scale=1080:-2[fg];[bg][fg]overlay=(W-w)/2:(H-h)/2,setsar=1" \
-c:v libx264 -pix_fmt yuv420p -c:a copy vertical.mp4
split duplicates the input stream into two labeled copies so the same source
can be processed two different ways in one filter graph. The [a] branch is
scaled up until it fully covers 1080×1920 (force_original_aspect_ratio=increase),
cropped to exact size, then blurred hard — that's the fill. The [b] branch is
scaled to fit the frame width at full quality (scale=1080:-2 keeps the aspect
ratio and rounds height to an even number, which libx264 requires) and overlaid centered.
-c:a copy leaves the audio untouched since this pass only touches video.
CRF and encoding notes
Neither command above sets -crf explicitly, so libx264 uses its default (23) —
fine for drafts. For a delivery-quality export, add -preset veryfast -crf 18 (the
same values the toolkit's frame-accurate cut script
uses for its final trim) to tighten quality at the cost of a larger file and slower encode.
Related
Read the free preview for the SOP Pack's table of contents and one full script-gate check.
The full pack ($19) has the worked example, prompts and QA receipts; the $9 toolkit is just the scripts.