ffmpegvideo-encodingvideo-processing

How to Upscale Video with FFmpeg (and When It Actually Helps)

··10 min read
How to Upscale Video with FFmpeg (and When It Actually Helps)

You have a 720p master and a platform spec that wants 1080p. Or a folder of clips shot at five different resolutions that refuse to join cleanly. Either way you need FFmpeg to make the frames bigger without making them look worse than they already do.

Quick answer: To upscale video with FFmpeg, run ffmpeg -i input.mp4 -vf "scale=-2:1080:flags=lanczos" -c:v libx264 -crf 18 -preset slow -c:a copy output.mp4, then raise the bitrate to match the new pixel count or the 1080p file will look softer than the 720p source it came from. FFmpeg upscale video commands hit a resolution target deterministically, but no scaler invents detail, so use this to meet a platform spec or to match resolutions before a concat, never to rescue a blurry clip. If you don't want to run encoders at all, FFmpeg Micro normalizes a whole library to one resolution as one API call per file, with no servers to run.

Conventional wisdom: upscaling improves a video. Most tools sold for the job promise exactly that, and the output does look different, so the belief sticks. But what you're buying isn't detail, it's interpolation. A scaler looks at the pixels you already have and guesses the ones in between by averaging. Lanczos averages with a nicer window than bilinear does, which is why it looks crisper. It still has nothing new to work with.

That matters because the thing people usually blame for a bad upscale is the filter, and the filter is almost never the problem. The bitrate is.

The FFmpeg command to upscale to 1080p or 4K

The scale filter with flags=lanczos handles both targets, and the only thing that changes between them is the height and how many bits you spend. Use -2 for the width so FFmpeg derives it from the source aspect ratio and rounds to an even number, which libx264 requires.

For 720p to 1080p:

ffmpeg -i input.mp4 \
  -vf "scale=-2:1080:flags=lanczos" \
  -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p \
  -c:a copy output_1080p.mp4

For 1080p to 4K:

ffmpeg -i input.mp4 \
  -vf "scale=-2:2160:flags=spline+accurate_rnd+full_chroma_int" \
  -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p \
  -c:a copy output_4k.mp4

The jump is larger than it feels. A 1280x720 frame is 921,600 pixels. 1920x1080 is 2,073,600, so 2.25 times as many. 3840x2160 is 8,294,400, nine times the 720p frame. Every one of those extra pixels is interpolated, and every one of them has to be encoded.

If a filter isn't in your chain at all and FFmpeg is scaling implicitly (because you set -s 1920x1080, say), the filter syntax won't apply. Set it globally instead with -sws_flags lanczos before the output file.

Which scaling flag to use: lanczos, spline, or bicubic

FFmpeg's swscale defaults to bicubic, which is fine and slightly soft. Lanczos is the usual pick for upscaling because it preserves edge contrast better, at the cost of mild ringing (faint halos) around hard edges. Spline trades a little of that sharpness for no ringing at all, which is why it holds up better on the very large jumps like 1080p to 4K.

FlagCharacterGood for
`neighbor`Blocky, no interpolationPixel art, deliberate retro look
`bilinear`Fast, visibly softPreviews, throwaway proxies
`bicubic`FFmpeg's default, mild softnessGeneral-purpose downscaling
`lanczos`Sharpest, slight ringing on edgesModest upscales: 720p to 1080p
`spline`Smooth, no ringingLarge upscales: 1080p to 4K

Pick one and stop. The gap between lanczos and spline on a 2.25x upscale is small enough that you'll spend more time A/B testing it than it's worth, and both of them are swamped by the next decision.

Bitrate is what decides whether the upscale looks good

Upscaling without raising the bitrate produces a file that looks worse than the source, which is the single most common way this goes wrong. You've multiplied the pixel count by 2.25 or by 9, and if -b:v stays where it was, each pixel gets a fraction of the bits it had before. The encoder responds by blurring and blocking exactly where the interpolated edges already needed help.

Using -crf instead of -b:v avoids the trap, because CRF targets a quality level and lets the file size move. The cost shows up on disk instead: a 720p clip re-encoded at 1080p and CRF 18 will often land two to three times larger than the original while containing no information the original didn't have.

If you're delivering to a fixed-bitrate spec, YouTube's own upload recommendations are a usable floor for SDR at 30 fps: 8 Mbps for 1080p, 16 Mbps for 1440p, and 35 to 45 Mbps for 2160p. A 1080p upload sitting at the 2.5 Mbps that was fine for your 720p master will look obviously degraded.

The sharpening pass that actually helps

An unsharp mask after the scale recovers some of the apparent crispness that interpolation smears, and it's the one post-upscale step worth adding. It doesn't add detail either, but it raises local contrast at edges, which is most of what people perceive as sharpness.

ffmpeg -i input.mp4 \
  -vf "scale=-2:1080:flags=lanczos,unsharp=5:5:0.6:5:5:0.0" \
  -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p \
  -c:a copy output.mp4

The six values are luma matrix width, luma matrix height, luma amount, then the same three for chroma. Matrix sizes must be odd numbers between 3 and 23. Keep luma amount between 0.4 and 0.8; past about 1.0 you get crunchy white outlines on every edge, and compression artifacts in the source get sharpened right along with the picture. Leaving chroma amount at 0.0 is deliberate, since sharpening chroma on 4:2:0 footage mostly amplifies color noise.

Order matters here. Scale first, sharpen second. Sharpening before the upscale just gives the scaler bigger halos to interpolate.

Normalizing mixed resolutions before a concat

The case where scaling genuinely earns its place in a pipeline is making a set of clips match before you join them. FFmpeg's concat demuxer with -c copy requires identical resolution, pixel format, timebase, and codec parameters across every input, and the moment one clip is 1080x1920 and the next is 720x1280 you get either a hard failure or a garbled second half.

Force every file through the same canvas, padding rather than stretching:

ffmpeg -i clip.mp4 \
  -vf "scale=1920:1080:force_original_aspect_ratio=decrease:flags=lanczos,\
pad=1920:1080:(ow-iw)/2:(oh-ih)/2:color=black,setsar=1,fps=30" \
  -c:v libx264 -crf 20 -preset medium -pix_fmt yuv420p \
  -c:a aac -b:a 192k -ar 48000 -ac 2 \
  normalized_clip.mp4

force_original_aspect_ratio=decrease fits the frame inside the canvas without distorting it, pad fills the rest, and setsar=1 kills the anamorphic pixel aspect ratios that make a correctly-sized file still render stretched. Fixing the frame rate and the audio sample rate in the same pass matters as much as the resolution does. Details on the concat side are in FFmpeg concat: merge videos, and the aspect-ratio arithmetic is covered in the scale filter and preserving aspect ratio.

Running that over 300 archive clips is where the local approach stops being pleasant: it's a long-running encode per file, and you're babysitting a machine. FFmpeg Micro does the same normalization as one API call per file from code, from n8n, Make, or Zapier, or from an AI agent over MCP, so the loop is just submit and collect. You can test the filter chain first in the playground.

When FFmpeg scaling is the wrong tool

Deterministic scaling is the wrong choice when the job is recovering detail that was never recorded. A 480p phone clip from 2013 upscaled to 1080p is a 480p clip with bigger pixels and a nicer edge treatment. If the deliverable genuinely needs to look like HD acquisition, that's a machine-learning super-resolution tool, which works by hallucinating plausible detail from a trained model. It sometimes looks great and it sometimes invents faces that aren't there. FFmpeg Micro does deterministic scaling, not super-resolution, and swscale does not do AI upscaling under any flag.

Scaling also won't fix heavy compression artifacts, interlacing combs (use yadif first), or motion blur. Clean the source, then scale. Never the other way around.

Common pitfalls

Upscaling failures cluster around a handful of mistakes, listed here in rough order of how often they bite.

  1. Odd dimensions. Ask for a height that produces an odd width and libx264 refuses: width not divisible by 2 (1081x1080) followed by Error while opening encoder for output stream #0:0. Use -2 instead of -1 for the free dimension so FFmpeg rounds to even.
  2. Hardcoding both dimensions on a non-matching source. scale=1920:1080 on 4:3 footage stretches everyone sideways. Use force_original_aspect_ratio=decrease plus pad.
  3. Keeping -b:v from the smaller render. Covered above, and it accounts for most "my upscale looks terrible" reports.
  4. Trying to stream-copy and filter at once. -vf and -c:v copy are mutually exclusive, and the error says so. See filtering and streamcopy cannot be used together.
  5. Forgetting -pix_fmt yuv420p. A 10-bit or 4:2:2 source will happily produce an output that QuickTime and most browsers won't play.
  6. Scaling before cropping. Crop first so the scaler only works on pixels you're keeping. Per-file bar detection is in FFmpeg cropdetect isn't one-shot.

FAQ

Does upscaling video with FFmpeg improve quality?

Upscaling with FFmpeg does not improve quality in the sense of adding detail. The scale filter interpolates new pixels from existing ones, so the output has more pixels carrying the same information. It improves compliance with a resolution spec and it makes files match each other, both of which are real wins.

Is lanczos better than bicubic for upscaling?

Lanczos produces a visibly sharper upscale than bicubic because its filter window preserves edge contrast more aggressively, with the trade-off of faint ringing around high-contrast edges. Bicubic is FFmpeg's default and renders slightly softer. For a 720p to 1080p job, lanczos is the better default.

What bitrate should I use when upscaling 1080p to 4K?

A 4K frame has four times the pixels of a 1080p frame, so a fixed bitrate must rise accordingly. YouTube recommends 35 to 45 Mbps for SDR 2160p at 30 fps against 8 Mbps for 1080p. Using CRF instead of a target bitrate sidesteps the calculation entirely, at the cost of an unpredictable file size.

Can FFmpeg do AI upscaling?

FFmpeg does not do AI upscaling through swscale or the standard scale filter. Every built-in scaler is deterministic interpolation, which means the same input always produces the same output and nothing is invented. Trained super-resolution models are a separate class of tool with a different set of risks.

How do I upscale video without stretching it?

To upscale without stretching, give the scale filter one fixed dimension and let FFmpeg derive the other: scale=-2:1080:flags=lanczos. If the output must be an exact canvas size regardless of source shape, add force_original_aspect_ratio=decrease and pad the remainder.

Resolution normalization across a library is the kind of work that's fine for ten files and miserable for a thousand. Sign up free and send the same scale and pad chain as one API call per file, with the job queue and the encoders on our side.

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.

Software EngineeringVideo ProcessingFFmpegCloud ArchitectureAPI DesignAutomation

Ready to process videos at scale?

Start using FFmpeg Micro's simple API today. No infrastructure required.

Get Started Free