Resizing videos with ffmpeg/avconv to fit into static sized player

83,007

Solution 1

A simple method is to use the force_original_aspect_ratio option in the scale filter.

original image
Original image. Represents a 640x480, 4:3 aspect ratio video.

In these examples the original image will be scaled to fit into a 1280x720, 16:9 aspect ratio output while preserving the original aspect ratio. To do this you can either:


Pillarbox or letterbox to fit

pillarboxed image
Pillarboxed image. Fitting a 640x480 (4:3) input into a 1280x720 (16:9) output.

ffmpeg -i input -vf "scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:-1:-1:color=black" output
  • This will upscale the image. If you want to avoid upscaling see the example below.

  • Letterboxing will occur instead of pillarboxing if the input aspect ratio is wider than the output aspect ratio. For example, an input with a 2.35:1 aspect ratio fit into a 16:9 output will result in letterboxing.

Same as above but without upscaling

no upscaling
640x480 (4:3) input into 1280x720 (16:9) output without upscaling.

ffmpeg -i input -vf "scale='min(1280,iw)':min'(720,ih)':force_original_aspect_ratio=decrease,pad=1280:720:-1:-1:color=black" output

Crop to fit

enter image description here
Cropped image. 4:3 input aspect ratio, 16:9 output aspect ratio.

Using the crop filter to cut off the excess:

ffmpeg -i input -vf "scale=1280:720:force_original_aspect_ratio=increase,crop=1280:720" output

Using input images that each vary in size

If you are inputting a series of images, and the images vary in size, add the eval=frame option in the scale filter, such as:

ffmpeg -i input -vf "scale=1280:720:force_original_aspect_ratio=decrease:eval=frame,pad=1280:720:-1:-1:color=black" output

Changing the background color

Use the color option in the pad filter. You can provide a hex value or use a supported color name.

Solution 2

Here's the command that'd add pillar- or letterboxing for a fixed output width. It's a tad long, but you'll have to specify the padding some way.

First, in your shell define output width and height:

width=700
height=400

Then run the command:

ffmpeg -i in.mp4 -filter:v "scale=iw*min($width/iw\,$height/ih):ih*min($width/iw\,$height/ih), pad=$width:$height:($width-iw*min($width/iw\,$height/ih))/2:($height-ih*min($width/iw\,$height/ih))/2" out.mp4

This is stripped down to the bare essentials needed to resize and pad—add your other video and audio options as you see fit. Note that the numbers for width and height have to be divisible by 2 in order to work for most codecs.

Here's the explanation of what's going on:

  • Scaling:
    • First we need to figure out whether to scale by width or height.
    • To do this, we divide the output width by the input width, and output height by input height. This will give us the scale factors for each dimension.
    • We then check which one is lower (with min()) and choose only that factor for resizing.
    • Finally, we multiply both input width and height by that factor (iw*min(…):ih*min(…)).
  • Padding:
    • $width:$height is the output width and height
    • To figure out where to place the resulting video, we need to subtract the scaled width from the maximum output width, and the scaled height from the maximum output height.
    • The scaled widths and heights are the expressions from the scale filter.
    • We divide the resulting offset by 2 to add borders at both sides.

Solution 3

It seems to me that you need to do this in three steps:

  1. Check the input aspect ratio
  2. Scale videos with a DAR > 7/4 width-wise (change the width to 700, and scale the height to keep the aspect ratio), and scale those with DAR < 7/4 height-wise
  3. Pad the video so that it fits in the 700:400 space.

FFmpeg/avconv can do the scaling/padding with video filters in a single step, transcoding only once. For example, to take a 16:9 video, scale it width-wise, and then letterbox the results:

ffmpeg -i input.avi -filter:v 'scale=700:-1,pad=700:400:(ow-iw)/2:(oh-ih)/2' \
-c:v libx264 -b:v 2000k -bufsize 20M -c:a aac -strict experimental -ar 44100 -b:a 256k output.mp4

...but for the first step (detecting the aspect ratio and comparing it to the 7:4 you require) you'll have to use a script of some kind.

ffprobe input.avi 2>&1 | sed -n '/Video:/s/.*DAR \([0-9]*:[0-9]*\)].*/\1/p'

...will get you the video's aspect ratio, which will look like '16:9' or '4:3'. In a bash script, I'd use something like:

#!/bin/bash

##  Get the aspect ratio in the form x/y
dar=$(ffprobe test0.mp4 2>&1 | sed -n '/Video:/s/.*DAR \([0-9]*:[0:9]*\)].*/\1/p' | sed 's|:|/|')
##  use bc to do x/y*100 (bash can't handle floats)
DAR=$(bc <<< 'scale=2; $dar*100')

##  ${DAR%.00} will remove the trailing .00 left by bc
if [ ${DAR%.00} -ge 175 ]; then
    ffmpeg -i "$1" -filter:v 'scale=700:-1,pad=700:400:(ow-iw)/2:(oh-ih)/2' \
    -c:v libx264 -b:v 2000k -bufsize 20M -c:a aac -strict experimental -ar 44100 -b:a 256k "${1%.*}.mp4
else
    ffmpeg -i "$1" -filter:v 'scale=-1:400,pad=700:400:(ow-iw)/2:(oh-ih)/2' \
    -c:v libx264 -b:v 2000k -bufsize 20M -c:a aac -strict experimental -ar 44100 -b:a 256k "${1%.*}.mp4
fi

exit 0

Obviously you'll have to adapt it to your needs.

Share:
83,007

Related videos on Youtube

Jamie Taylor
Author by

Jamie Taylor

Updated on September 18, 2022

Comments

  • Jamie Taylor
    Jamie Taylor almost 2 years

    I have a html 5 video player which is 700px wide, 400px high. I'm trying to use avconv to use ffmpeg to resize (while retaining the aspect ratio) and making sure it fits into my player.

    Input can be a file of any size, so I need to resize the larger ones but center the smaller ones with black bars. So far I've tried: -s and -aspect, I've seen pad being used with the -vf switch but don't understand how it works enough to get what I need.

    Images

    This is a rough idea of what I need. I'm not sure if it's even possible. It's almost like CSS's max-width/max-height. I realise this may just be a jumble of words but if anyone happens to understand what I'm talking about, I'd appreciate help, Thanks.

    avconv command:

    avconv -y -i control.avi -vcodec libx264 -b 2000k -bufsize 20M -acodec aac -strict experimental -ar 44100 -ab 256k bigbuck_out.mp4
    
    • slhck
      slhck over 11 years
      I'll write up an answer later, but I thought I'd mention the following: Re-encoding a video just to add fixed pillar- or letterboxing is a terrible idea. Not only will you lose quality in that process (or unnecessarily increase file size), you will also make it impossible to get rid of the bars later. Ideally your player should simply take care of that and resize the video while displaying it. What is your use case if I may ask?
    • Jamie Taylor
      Jamie Taylor over 11 years
      We stash the original file in an S3 bucket, so that's no problem, and we're re-encoding anyway as we accept a large scope of video types, all of these have to end up as h.264 mp4 files suitable for streaming through a browser. So you'd suggest resizing the video and then making html/css pillar/letterboxes? Thanks
    • slhck
      slhck over 7 years
      As the owner of the accepted answer I'd suggest you un-accept my post and accept LordNeckbeard's answer instead, as it is the most recent and much simpler.
    • Jamie Taylor
      Jamie Taylor over 7 years
      @slhck, I was considering it, with your blessing, I'll make it so.
  • slhck
    slhck over 11 years
    It's possible to do that in a single step though, see my answer.
  • Elisa Cha Cha
    Elisa Cha Cha over 11 years
    @JamieTaylor Consider adding -movflags +faststart to slhck's example. It will allow your progressively downloaded video to begin playback before it is completely downloaded, and also see the FFmpeg and x264 Encoding Guide and FFmpeg and AAC Encoding Guide for more encoding instructions and examples.
  • jdi
    jdi over 9 years
    You could shorten the pad filter to this, I think: pad=$width:$height:(ow-iw)/2:(oh-ih)/2"
  • slhck
    slhck over 9 years
    @jdi This won't work if you are downscaling, because it'll result in negative values for ow-iw.
  • schieferstapel
    schieferstapel over 8 years
    Thanks for the starting point! However, to get a proper aspect ratio for videos with a sample aspect ratio != 1, every occurance of "iw" needs to be multiplied by "sar", so replacing EVERY occurance of "iw" with "(sar*iw)" in the filter expression works for me.
  • Nisarg
    Nisarg over 7 years
    @slhck Hey, I am trying in android but it doesn't seem to be working,can you help me with it !!! ffmpeg -i input.mp4 -filter:v scale=640*min(700/640,400/360):360*min(700/640,400/360),pad=‌​700:400:(700-640*min‌​(700/640,400/360))/2‌​:(400-360*min(700/64‌​0,400/360))/2 output.mp4
  • Nisarg
    Nisarg over 7 years
    @slhck Please check this
  • Elisa Cha Cha
    Elisa Cha Cha over 7 years
    @slhck Now it's easier. See answer with guitar-mullet.
  • slhck
    slhck over 7 years
    @LordNeckbeard Thanks! I recently saw that option, makes it much easier indeed.
  • Tiago Ferezin
    Tiago Ferezin about 5 years
    And for me to add a blank fill?
  • Tiago Ferezin
    Tiago Ferezin about 5 years
    oh sorry, Fill with another color instead of black, as example
  • Elisa Cha Cha
    Elisa Cha Cha about 5 years
    @TiagoFerezin Ah, yes. Easy to do. In the examples that use the pad filter you can add the color option, such as pad=1280:720:(ow-iw)/2:(oh-ih)/2:color=red (or you can use the hexadecimal if you prefer). See list of colors.
  • ggrelet
    ggrelet over 3 years
    What does the eval=frame do, exactly?
  • Elisa Cha Cha
    Elisa Cha Cha over 3 years
    @ggrelet It makes scale filter take a look at each frame. Otherwise it will assume all frames have the same width x height as the initial frame. You can read more details in the scale filter documentation.
  • 100rabh
    100rabh almost 3 years
    Works like a charm. I've been struggling with solutions on SO but this one helped!
  • Mint
    Mint over 2 years
    If you want to align to top with 'Same as above but without upscaling" change -1:-1 to -1:0, the 2nd number being the vertical position. (this took me way longer to figure out then I'd like to admit)