Resizing videos with ffmpeg/avconv to fit into static sized player
A simple method is to use the force_original_aspect_ratio
option in the scale filter.
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:
- Add black bars (or any other color) with pad filter to pillarbox or letterbox the image to fit properly, or
- Use the crop filter to cut off the excess
Pillarbox or letterbox to fit
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
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
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.
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.
It seems to me that you need to do this in three steps:
- Check the input aspect ratio
- 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
- 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.