Fix FFmpeg's "height not divisible by 2" Error on User Uploads

A user uploads a screen recording. Your pipeline scales it to 720p, hands the frames to libx264, and the job dies before it writes a single frame: height not divisible by 2 (720x959). Nothing is wrong with the file, and nothing is wrong with your command except one missing rounding rule.
Quick answer: The FFmpeg error "height not divisible by 2" comes from libx264 encoding to the yuv420p pixel format, which stores chroma at half resolution and therefore needs both output dimensions to be even. Fix it in the filter chain withscale=-2:720when you're preserving aspect ratio,crop=trunc(iw/2)*2:trunc(ih/2)*2when you'll accept losing one row or column, orpad=ceil(iw/2)*2:ceil(ih/2)*2when you can't lose a pixel. If you'd rather not carry that rule into every job you write, FFmpeg Micro's hosted resize endpoint rounds output dimensions to even numbers by default, so arbitrary phone and screen-recording uploads never fail encoder init.
Why yuv420p forces even width and height
The constraint lives in the pixel format, not in FFmpeg and not in your file. In yuv420p, the two chroma planes are subsampled by two in both directions, so a 720x959 luma plane would need a 360x479.5 chroma plane. There's no such thing as half a chroma sample, so libx264 refuses the frame size rather than emit a stream nobody can decode.
The H.264 spec backs it up. Coded frames are made of 16x16 macroblocks, and the cropping offsets that trim a coded frame down to its display size are counted in chroma samples for 4:2:0 content. Those offsets are always multiples of two, which means an odd display height is literally unrepresentable in a 4:2:0 H.264 stream.
On FFmpeg 7.x the failure prints:
[libx264 @ 0x7f9e1c008200] height not divisible by 2 (720x959)
[vost#0:0/libx264 @ 0x7f9e1c007a80] Error while opening encoder - maybe incorrect
parameters such as bit_rate, rate, width or height.
Error while filtering: Generic error in an external library
On FFmpeg 4.x and 5.x the same thing prints as Error initializing output stream 0:0 -- Error while opening encoder for output stream #0:0. Either way it's an encoder initialization failure, which matters for pipeline design: the job dies at second zero, after you've paid to fetch the source and before you've produced anything. It fails fast, but it fails on every affected upload forever, because the cause is the source dimensions and those don't change on retry.
The three fixes and when each one is right
There are three ways to force even dimensions, and they trade off differently: one resamples, one throws a pixel away, one adds a pixel. Measured on a 1470x945 window capture, the difference is small but not invisible.
| Filter | Output | What it costs |
|---|---|---|
| `scale=-2:720` | 1120x720 | Full resample. Aspect held at 1.5556 |
| `crop=trunc(iw/2)*2:trunc(ih/2)*2` | 1470x944 | Bottom row dropped. Aspect drifts to 1.5572 |
| `scale=trunc(iw/2)*2:trunc(ih/2)*2` | 1470x944 | Same size as the crop, but the whole image is resampled |
| `pad=ceil(iw/2)*2:ceil(ih/2)*2` | 1470x946 | One black row added at the bottom |
scale=-2 is the default when you're already resizing
Use scale=-2:720 (or scale=1080:-2) any time you're resizing and want the aspect ratio preserved. The negative value tells FFmpeg to compute that dimension from the other one and round it to a multiple of two.
ffmpeg -i upload.mp4 -vf "scale=-2:720" -c:v libx264 -pix_fmt yuv420p out.mp4
The trap is -1, which does the same aspect math with no rounding rule at all. On a 1080x1439 phone screen recording, scale=720:-1 computes 959.33 and hands libx264 a 720x959 frame. scale=720:-2 computes the same number and rounds to 960. One character.
crop or trunc when you'll accept losing a pixel
Reach for truncation when the output size is dictated by the source and you're not resizing. trunc(iw/2)*2 rounds each dimension down to the nearest even number, so 945 becomes 944.
ffmpeg -i upload.mp4 -vf "crop=trunc(iw/2)*2:trunc(ih/2)*2" \
-c:v libx264 -pix_fmt yuv420p out.mp4
Most guides write this with scale instead of crop, and it works, but it resamples all 1.4 million pixels to shave one row. crop drops the row and leaves every remaining pixel bit-identical, which matters for screen recordings where text sharpness is the whole point. Use scale=trunc(iw/2)*2:trunc(ih/2)*2 only when a scale filter is already in the chain for other reasons.
pad when you can't lose a pixel
Padding is right when the frame content is fixed and cropping would clip something you need, like a slide deck, a product shot framed to the edge, or a chart with an axis label on the last row.
ffmpeg -i upload.mp4 -vf "pad=ceil(iw/2)*2:ceil(ih/2)*2,setsar=1" \
-c:v libx264 -pix_fmt yuv420p out.mp4
ceil rounds up, so 945 becomes 946 and the new row is filled with black by default. On a light-background screen recording that single black line reads as a visible seam along the bottom edge, so pass a matching fill: pad=ceil(iw/2)*2:ceil(ih/2)*2:0:0:color=white. The setsar=1 matters too, because a padded frame that inherits a non-square sample aspect ratio from an anamorphic source will get stretched by the player.
Which codecs actually impose the constraint
The rule follows chroma subsampling, which is why the same odd-dimension source encodes fine to some targets and fails on others. This is what confuses people who test with a ProRes intermediate and hit the error only in production.
| Encoder and pixel format | Even width | Even height |
|---|---|---|
| libx264 / libx265, yuv420p | Required | Required |
| libx264, yuv422p | Required | Not required |
| libx264, yuv444p | Not required | Not required |
| ProRes (422 family) | Required | Not required |
| MJPEG in AVI, yuvj444p | Not required | Not required |
| h264_nvenc and other hardware encoders | Required | Required |
| libvpx-vp9, yuv420p | Not required | Not required |
ProRes 422 subsamples chroma horizontally only, so an odd height is fine and an odd width still isn't. MJPEG at 4:4:4 doesn't subsample at all, which is why an old AVI workflow will happily swallow 1470x945 and an MP4 workflow won't. VP9 and AV1 define odd frames by rounding the chroma plane size up with (w+1)>>1, so the same source can produce a working WebM and a failed MP4 in the same run.
Hardware encoders are stricter, not looser. Arbitrary input resolutions come up repeatedly on NVIDIA's developer forums, and the answer is the same: round before the frame reaches the encoder, because NVENC won't do it for you.
One thing not to do: switching -pix_fmt yuv420p to yuv444p makes the error disappear and breaks playback nearly everywhere. 4:4:4 H.264 requires the High 4:4:4 Predictive profile, which QuickTime, Safari, and most browser <video> implementations won't decode. You'll trade a loud build-time failure for a silent black player on half your users' devices.
Where odd dimensions come from in an upload pipeline
If you control your inputs, you'll never see this error. If you accept uploads, you'll see it within the first few hundred jobs, and always from one of four places.
Ratio scaling is the most common. scale=iw/3:ih/3 on a 1280x720 clip computes 426.67x240, FFmpeg rounds the width to 427, and libx264 rejects it with the width version of the same message. Any divisor that isn't a clean factor of both dimensions will do this eventually.
Aspect-ratio cropping is next. Cutting a 1080-pixel-wide vertical video to 16:9 needs a height of 607.5, and whichever way you round, half the time you land on an odd number. Face-box and subject-detection crops are worse, because the box comes from a model and has no reason to be even at all.
Then there's arbitrary source footage. Phone video is almost always even, but window captures, cropped exports out of an editor, GIF conversions, and anything that's been through a resize in a browser are not. Screen recordings of a scaled display are a reliable source of odd heights.
Encoding that rule into every command works right up until you have a dozen commands. If the video step is one part of a larger workflow, normalizing dimensions once at the boundary is cheaper: FFmpeg Micro runs FFmpeg as a hosted API and its resize enforces even output dimensions by default, so an odd-height upload comes back as a playable MP4 instead of an encoder error you have to catch and retry. You can throw a deliberately odd-dimension file at it in the playground before wiring anything up.
Common pitfalls
The rounding filter has to be the last video filter in the chain. Put scale=-2:720 before a crop and the crop reintroduces an odd dimension downstream, and the error you get back names dimensions that appear nowhere in your command.
Keep your crop offsets even too. crop=w:h:x:y with an odd x or y shifts the luma plane by one pixel while chroma can only move in steps of two, which produces a faint color fringe on high-contrast edges. Wrap them the same way: crop=trunc(iw/2)*2:trunc(ih/2)*2:trunc(x/2)*2:trunc(y/2)*2.
When you're normalizing many sources to one canvas, don't stack rounding filters. Use the built-in combination instead, which handles both the fit and the even-dimension requirement in one pass because 1080 and 1920 are already even:
ffmpeg -i upload.mp4 -vf "scale=1080:1920:force_original_aspect_ratio=decrease,\
pad=1080:1920:(ow-iw)/2:(oh-ih)/2,setsar=1" -c:v libx264 -pix_fmt yuv420p out.mp4
This is also the fix for the stacking filters, which are pickier than the encoder: hstack needs identical heights and pixel formats across inputs and vstack needs identical widths, so normalize first as covered in putting two videos side by side. The same normalize-first habit is what keeps a batch transcode across a folder from dying on file 340 of 500.
Last one: -vf and -filter_complex don't combine. If you're already using -filter_complex for an overlay or a concat, the rounding has to go inside it, appended to the final filter's output chain, not tacked on as a separate -vf.
FAQ
Why does FFmpeg say height not divisible by 2 when my source video plays fine?
The error is about the output dimensions, not the input. Your source plays fine because whatever encoded it either used even dimensions or used a format without 4:2:0 chroma subsampling, and your FFmpeg command is producing an odd height on the way out, usually from a scale or crop expression that computed a fractional value.
What's the difference between scale=-1 and scale=-2 in FFmpeg?
scale=-1 tells FFmpeg to compute that dimension from the aspect ratio and use it as-is, odd numbers included. scale=-2 does the same math and rounds the result to a multiple of two, which is why -2 is the value to default to for any H.264 output.
Does the height not divisible by 2 error mean my file is corrupt?
No. The error is raised by libx264 during encoder initialization, before it touches your frames, purely on the basis of the requested output size. A corrupt input produces different errors entirely, like a missing moov atom or a decode failure partway through.
How do I make FFmpeg round dimensions to even numbers automatically?
FFmpeg won't round automatically, so you have to say so in the filter chain. Use scale=-2:<height> when resizing, or scale=trunc(iw/2)*2:trunc(ih/2)*2 when you want to keep the source size and just force it even.
Can I avoid the error by switching to yuv444p?
Switching to yuv444p removes the even-dimension requirement because 4:4:4 doesn't subsample chroma, but it forces the High 4:4:4 Predictive profile, which Safari, QuickTime, and most browser video players can't decode. Round the dimensions instead and keep yuv420p.
Normalizing dimensions is the kind of rule that's easy to write once and expensive to forget in job number seven. If you'd rather have it enforced at the boundary than re-derived in every command, sign up free and send an odd-height file through the resize endpoint to see what comes back.
About Javid Jamae
Founder & CEO at FFmpeg Micro
Javid is a software engineer, author, and entrepreneur with over 25 years of professional software development experience across enterprise, startup, and consulting environments. He founded FFmpeg Micro to make video processing accessible to developers through a simple, automation-first REST API.
You might also like

Fix "moov atom not found" on Uploaded Videos in Your Pipeline
"moov atom not found" almost never means a corrupt video. It means a truncated upload or a Range request that missed the trailing moov box. Check and fix it.

FFmpeg Two-Pass Encoding vs CRF: When It Actually Helps
FFmpeg two-pass encoding hits an exact file size that CRF can't guarantee. The -pass 1 and -pass 2 commands, when two-pass beats CRF, and the real pitfalls.

Stop Re-Exporting. Encoding a Video Once Covers Every Platform.
Encoding a video once and deriving every delivery version from it: the mezzanine command, CRF and preset per rung, social crops, and the one-call version.
Ready to process videos at scale?
Start using FFmpeg Micro's simple API today. No infrastructure required.
Get Started Free