Scale
Video Single output Transform
Make a video come out at a specific size — shrink a 4K clip to 1080p, fit phone footage into a
widescreen frame, or build a thumbnail. scale is the filter you’ll reach for most, and while
it’s resizing it can quietly smooth over colour and format differences between clips. Skim the
parameter table at the bottom if you just need a reminder, or read top-to-bottom to actually
understand what each knob does.
FFmpeg filter:
scale— the official reference.
Add it to a graph. The explicit form works for every filter — bind it to its input and read the result from outputs():
$node = $video->addFilter(new Scale(…));$result = $node->outputs()[0];Shorthand. Single-output filters can skip the node — apply() returns the result stream directly:
$result = $video->apply(new Scale(…));When to use it
Section titled “When to use it”Reach for scale whenever the output needs to be a particular size. A few everyday
situations:
- “I have a 4K video and need a 1080p version.” Shrinking to a delivery resolution is what
scaledoes all day. - “My clips are different sizes and I want to stack or overlay them.” Compositing filters expect their inputs to line up; scale each clip to a common size first.
- “I need a thumbnail.” Scale a frame down to a small fixed size.
- “This footage is the wrong shape for where it’s going.” Scale can fit it — ideally without distorting it (there’s a recipe for that below).
One term worth pinning down up front: aspect ratio is just the width-to-height shape of the
frame — 16:9 is widescreen, 4:3 is the older, more square TV shape. Most resizing mishaps come
from changing the size while ignoring the aspect ratio, which leaves everyone looking stretched or
squashed. Scale gives you a couple of ways to avoid that, covered in the next section.
When another filter is the better tool:
- To cut away part of the frame (zoom into a corner, trim a letterbox someone already baked in)
rather than squash the whole picture, you want to crop — see
Crop. - To keep the whole picture but add black bars so it fits a different shape (the classic
“letterbox”), scale it to fit and then
Padthe bars on. That recipe is in the examples. - To only re-label the aspect ratio without resampling a single pixel, use
Setsar/Setdar. - For colour-critical work — HDR, wide-gamut, careful tone-mapping —
Zscaleis the more precise (and slower) resizer. For everyday SD and HD video,scaleis the right, fast choice.
How to use it
Section titled “How to use it”scale’s behaviour comes down to a handful of parameters. Here’s what each one actually does.
Setting the size — width and height. Pass plain numbers (1280, 720), a small formula
as a string ('iw/2' means “half the input width”), or one of two special values that keep the
shape intact:
-1says “work this dimension out for me so the aspect ratio stays the same.” Ask for a height of720and a width of-1, and scale picks the width that keeps the picture from looking stretched.-2does the same but rounds to an even number — and you should usually prefer it. Here’s why: the most common delivery format, H.264 withyuv420p(the standard way web video stores its colour), can’t handle odd pixel dimensions. A width of-1might land on 641, and the encode then fails outright.-2guarantees an even number, so you stay safe.
Leave both unset and the size is unchanged — useful when you only want scale for its colour or
format conversion.
Formulas, and when they’re worked out — eval. Width/height formulas can refer to the input
size: iw/ih (input width/height), plus a (the aspect ratio), and a few others. By default
(eval: ScaleEval::Init) the formula is calculated once, at the start. Switch to
eval: ScaleEval::Frame only if your size is meant to change over time and you want it
recalculated every frame — it costs a little per frame, so don’t reach for it otherwise.
Choosing the quality — flags. When scale resizes, it has to invent the in-between pixels, and
flags picks the maths it uses — a trade of sharpness against speed. 'bilinear' (the default) is
fast and slightly soft; 'lanczos' is noticeably sharper and a great all-rounder for both shrinking
and enlarging; 'neighbor' is the fastest but looks blocky; 'area' is excellent for shrinking.
Fitting into a box without distortion — force_original_aspect_ratio + force_divisible_by.
Give scale a width×height box and let it keep the shape: ScaleForceOar::Decrease shrinks the
picture until it fits inside the box (you then usually Pad the leftover space with bars);
ScaleForceOar::Increase grows it until it covers the box. Add force_divisible_by: 2 to round
the result to an encodable (even) size.
Colour — in_range/out_range and in_color_matrix/out_color_matrix. These convert between
colour conventions, and they’re worth understanding before you touch them:
- Range is how the black-to-white levels are stored.
limited(a.k.a. “TV” range) keeps a little headroom at the top and bottom;full(“PC” range) uses the entire scale. Most video is limited range. - Matrix (
bt709,bt2020, …) is the standard that defines how colour itself is encoded. HD video almost always uses BT.709; standard-definition used an older one; modern wide-gamut and HDR use BT.2020.
The golden rule: leave these alone unless you are deliberately converting. Forcing the wrong
range or matrix is a classic cause of washed-out or muddy-looking footage. (The remaining
*_chroma_loc / *_chr_pos options control exactly where colour samples sit relative to the
brightness samples — genuine edge-case territory.)
param0 / param1 fine-tune particular scalers; interl enables interlaced-aware scaling
for old interlaced footage. You’ll rarely touch either.
And one thing scale deliberately does not do: it never changes the frame rate or the duration —
only the size and format.
Examples
Section titled “Examples”Downscale to 720p, keep the shape, stay encodable. The -2 keeps the aspect ratio and rounds
to an even width:
use FFmpeg\Media;use FFmpeg\Filter\Scale;
$video = Media::open('input.mp4')->videoStream();$scaled = $video->apply(new Scale(width: -2, height: 720));ffmpeg -i input.mp4 -vf "scale=-2:720" output.mp4Half size, using a formula:
$half = $video->apply(new Scale(width: 'iw/2', height: 'ih/2'));ffmpeg -i input.mp4 -vf "scale=iw/2:ih/2" output.mp4Letterbox into 16:9 without distortion (scale to fit, then pad the bars). This is the recipe
for “make this fit a 1280×720 frame but don’t stretch it” — scale shrinks the picture to fit
inside the box, then Pad fills the leftover space with black bars and centres the picture:
use FFmpeg\Filter\{Scale, Pad};use FFmpeg\Filter\Enum\ScaleForceOar;
$letterboxed = $video ->apply(new Scale(width: 1280, height: 720, force_original_aspect_ratio: ScaleForceOar::Decrease)) ->apply(new Pad(width: 1280, height: 720, x: '(ow-iw)/2', y: '(oh-ih)/2'));ffmpeg -i input.mp4 -vf "scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:(ow-iw)/2:(oh-ih)/2" output.mp4The “Ken Burns” effect — turn a still into a slow zoom-and-pan. The move you see over photos in
documentaries. Seeing it as a chain is the whole point: scale up so the push-in stays sharp,
Crop to the delivery shape first so the zoom can’t stretch a non-16:9 source into an oblong,
Zoompan to ease in and drift across the frame, then Scale down to the final size:
use FFmpeg\Media;use FFmpeg\Filter\{Scale, Zoompan, Crop};
$still = Media::open('blue-marble.png')->videoStream(); // a single still image
$kenBurns = $still ->apply(new Scale(width: 'iw*2', height: 'ih*2')) // headroom so the zoom stays sharp ->apply(new Crop(out_w: 'iw', out_h: 'iw*9/16')) // crop to 16:9 first so it can't be stretched ->apply(new Zoompan( zoom: 'min(1.05+0.35*on/149,1.4)', // ease 1.05×→1.4× across the whole clip (ends with the pan) x: '(iw-iw/zoom)*(on/149)', // pan left→right across the slack the zoom opens up y: '(ih-ih/zoom)*0.5', // hold vertically centred d: 150, // a still is ONE frame, so d = frames to emit (150 @ 30fps = 5s) s: '1280x720', // 16:9 in → 16:9 out: no distortion fps: '30', )) ->apply(new Scale(width: 960, height: 540));ffmpeg -i blue-marble.png -vf "scale=iw*2:ih*2,crop=iw:iw*9/16,zoompan=z='min(1.05+0.35*on/149,1.4)':x='(iw-iw/zoom)*(on/149)':y='(ih-ih/zoom)*0.5':d=150:s=1280x720:fps=30,scale=960:540" output.mp4For a moving video source instead of a still, use d: 1 (one output frame per input frame) and
drop fps. The clip below is exactly the snippet above, rendered by ext-ffmpeg itself (no
command line involved) from NASA’s public-domain “Blue Marble”:
Convert colour to BT.709 limited range (e.g. normalising an older BT.601 clip for HD delivery):
use FFmpeg\Filter\Enum\{ScaleColor, ScaleRange};
$converted = $video->apply(new Scale( width: 1280, height: 720, out_color_matrix: ScaleColor::Bt709, out_range: ScaleRange::Limited,));Gotchas
Section titled “Gotchas”- Odd dimensions fail at encode. H.264 with
yuv420pneeds an even width and height.width: -1keeps the ratio exactly but can land on an odd number; prefer-2, or addforce_divisible_by: 2. - Enlarging can’t add detail. Scaling up only stretches what’s already there —
flags: 'lanczos'looks sharper than the default, but there’s no extra detail to recover that the camera didn’t capture. - Only touch the colour options when you mean to. The range and matrix options default to automatic for a reason; forcing a mismatched one shifts the levels (milky blacks, blown highlights). When in doubt, leave them unset.
eval: ScaleEval::Frameis for animation only. It recalculates the size every frame; don’t enable it for a size that never changes.scaleis size and format, nothing else. It won’t change the frame rate or duration — use the dedicated filters for those.
See also
Section titled “See also”Crop— remove pixels (change the framing) instead of resampling the whole picture.Pad— add letterbox/pillarbox bars; pairs withforce_original_aspect_ratio: Decreaseto fit then bar out the remainder (see the letterbox example).Setsar/Setdar— change the stored aspect-ratio label without resampling.Zoompan— the zoom/pan engine behind the Ken Burns example.Zscale— the zimg scaler, for stricter colourspace and HDR conversions.
Parameters
Section titled “Parameters”| Parameter | Type | Default | Range / values | Description |
|---|---|---|---|---|
width | int|float|string | — | expression | Output video width |
height | int|float|string | — | expression | Output video height |
flags | string | — | — | Flags to pass to libswscale |
interl | bool | false | — | set interlacing |
size | string | — | — | set video size |
in_color_matrix | ScaleColor | auto | auto, smpte170m, bt709, fcc, smpte240m, bt2020 | set input YCbCr type |
out_color_matrix | ScaleColor | 2 | auto, smpte170m, bt709, fcc, smpte240m, bt2020 | set output YCbCr type |
in_range | ScaleRange | unknown | unknown, full, limited | set input color range |
out_range | ScaleRange | unknown | unknown, full, limited | set output color range |
in_chroma_loc | ScaleChromaLoc | unknown | unknown, left, center, topleft, top, bottomleft, bottom | set input chroma sample location |
out_chroma_loc | ScaleChromaLoc | unknown | unknown, left, center, topleft, top, bottomleft, bottom | set output chroma sample location |
in_v_chr_pos | int | -513 | -513–512 | input vertical chroma position in luma grid/256 |
in_h_chr_pos | int | -513 | -513–512 | input horizontal chroma position in luma grid/256 |
out_v_chr_pos | int | -513 | -513–512 | output vertical chroma position in luma grid/256 |
out_h_chr_pos | int | -513 | -513–512 | output horizontal chroma position in luma grid/256 |
force_original_aspect_ratio | ScaleForceOar | disable | disable, decrease, increase | decrease or increase w/h if necessary to keep the original AR |
force_divisible_by | int | 1 | 1–256 | enforce that the output resolution is divisible by a defined integer when force_original_aspect_ratio is used |
param0 | float | — | — | Scaler param 0 |
param1 | float | — | — | Scaler param 1 |
eval | ScaleEval | init | init, frame | specify when to evaluate expressions |
FFmpeg also names these options (use the parameter shown above): w → width, h → height, s → size.
Maps to FFmpeg’s scale filter. Verified against ffmpeg n7.1.1.
An Artisan Build project.
Built on FFmpeg — an independent binding, not affiliated with or endorsed by the FFmpeg project. Support FFmpeg.