23

I need to fit videos to 640x360 (the maximum my phone's player can handle), while also preserving aspect ratio, but I also want the video to be unchanged if it is smaller than 640x360 (no point in up-scaling it after all).

Is there a way to get this behavior using ffmpeg's command line?

3
  • I don't think this can be done solely in ffmpeg, but if you're willing to script it, it can definitely be done.
    – evilsoup
    Commented Mar 16, 2013 at 17:15
  • I've already scripted it, but I wanted to clean up my code in case it' s not needed.
    – sashoalm
    Commented Mar 16, 2013 at 17:16
  • It's probably possible with a scale filter that uses functions such as min(…) but most definitely easier with a simple script that parses the dimensions. See my command here for an example of what can be done: superuser.com/questions/547296/…
    – slhck
    Commented Mar 16, 2013 at 18:47

3 Answers 3

25

With newer ffmpeg versions, you can use the scale filter's force_original_aspect_ratio option. For example, to fit a video into 1280×720, without upscaling (see this post for more info):

ffmpeg -i input.mp4 -filter:v "scale='min(1280,iw)':min'(720,ih)':force_original_aspect_ratio=decrease,pad=1280:720:(ow-iw)/2:(oh-ih)/2" output.mp4

Here, the scale filter scales to 1280×720 if the input video is larger than that. If it is smaller, it will not be upscaled. The pad filter is necessary to bring the output video to 1280×720, in case its aspect ratio or size differs from the target size.


With older ffmpeg versions, there is a somewhat hacky workaround. First, define the width, height and aspect ratio of your output. This will save us some typing.

width=640; height=360
aspect=$( bc <<< "scale=3; $width / $height") # <= floating point division

Now, let's apply the super complex filter command that Jim Worrall wrote:

ffmpeg -i input.mp4 -vf "scale = min(1\,gt(iw\,$width)+gt(ih\,$height)) * (gte(a\,$aspect)*$width + \
lt(a\,$aspect)*(($height*iw)/ih)) + not(min(1\,gt(iw\,$width)+gt(ih\,$height)))*iw : \
min(1\,gt(iw\,$width)+gt(ih\,$height)) * (lte(a\,$aspect)*$height + \
gt(a\,$aspect)*(($width*ih)/iw)) + not(min(1\,gt(iw\,$width)+gt(ih\,$height)))*ih" \
output.mp4

I won't really go into explaining what this all does, but basically you can feed it any video, and it will only downscale, not upscale. If you're up for it you can dissect the filter into its individual expressions. It might be possible to shorten this, but it works like that as well.

6
  • 3
    +1 but that is a truly horrific command :P
    – evilsoup
    Commented Mar 19, 2013 at 21:33
  • I know right? I spent ten minutes trying to break it into logical parts and then inserting some values but I gave up. It's a little old and maybe it'd be possible to write it much more concisely than this though.
    – slhck
    Commented Mar 19, 2013 at 21:36
  • I really don't understand why the single quotes are where they are: scale='min(1280,iw)':min'(720,ih)'
    – maf-soft
    Commented May 3, 2021 at 15:35
  • @maf-soft Because the comma is usually used by ffmpeg to separate filters in a filter chain (in the above example, between scale and pad). By quoting it, ffmpeg will not interpret the comma that way, but use it as part of the first option passed to scale, which is min(1280,iw).
    – slhck
    Commented May 4, 2021 at 8:10
  • 1
    @maf-soft In principle, yes, you can place those anywhere, but that kind of quoting does not make sense to me at all – at least it makes the command very hard to read.
    – slhck
    Commented May 4, 2021 at 13:31
22

A more readable version can look like follows:

-filter_complex "scale=iw*min(1\,min(640/iw\,360/ih)):-1"

640/iw is the horizontal scaling factor and 360/ih is the vertical scaling factor

You want to fit the scaled image inside the output box and keep the (storage) aspect ratio. You do this by selecting the smallest scaling factor with the minimum function: min(640/iw, 360/ih)

You want to prevent any upscaling (i.e. a scaling factor > 1.0) so you add another minimum function: min(1, min(640/iw, 360/ih))

Next step is to calculate the output resolution by multiplying the scaling-factor with input-width and input-height:
output-width = iw * min(1, min(640/iw, 360/ih))
output-height = ih * min(1, min(640/iw, 360/ih))

Last step is to construct the filter command. There is no need to specify the output-height, you can specify -1 and ffmpeg will keep the aspect ratio by applying the same scaling factor as for the width.

3
  • 1
    A+ Works out of the box. Selected answer's solution did not preserve aspect ratio. It squeezed the frames.
    – user287352
    Commented Jun 7, 2019 at 19:05
  • 1
    FYI for everyone else looking into this, if you run into issues where you get 'height not divisible by 2' do this one instead: ceil(iw*min(1\,min(640/iw\,360/ih))/2)*2:-1, it adds 1px if the concerning edge is odd.
    – lawonga
    Commented Jul 13, 2020 at 0:48
  • @lawonga, I would think you would want to use floor in most cases since typically you're downscaling. Using ceil allowed my final video to actually be a pixel larger than the max size set. This is the command I ended up using: -vf "scale=floor(iw*min(1\,min(640/iw\,360/ih))/2)*2:-1" (You can experience what I mentioned by setting the 640 and 360 values to odd values like 643 & 301 respectively, for example.) Commented May 29 at 13:38
5

I had same problem too, but solved by fitting the video in a square 640x640 (because of vertical videos made with smartphones).

So using immerzi logic and some research I end up with this:

-vf "scale=iw*min(1\,if(gt(iw\,ih)\,640/iw\,(640*sar)/ih)):(floor((ow/dar)/2))*2"

the last part is for having a height divisible by 2 that is need by many encoders.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .