Convert a video to a fixed screen size by cropping and resizing

56,673

Solution 1

I'm no ffmpeg guru, but this should do the trick.

First of all, you can get the size of input video like this:

ffprobe -v error -of flat=s=_ -select_streams v:0 -show_entries stream=height,width in.mp4

With a reasonably recent ffmpeg, you can resize your video with these options:

ffmpeg -i in.mp4 -vf scale=720:480 out.mp4

You can set the width or height to -1 in order to let ffmpeg resize the video keeping the aspect ratio. Actually, -2 is a better choice since the computed value should even. So you could type:

ffmpeg -i in.mp4 -vf scale=720:-2 out.mp4

Once you get the video, it may be bigger than the expected 720x480 since you let ffmpeg compute the height, so you'll have to crop it. This can be done like this:

ffmpeg -i in.mp4 -filter:v "crop=in_w:480" out.mp4

Finally, you could write a script like this (can easily be optimized, but I kept it simple for legibility):

#!/bin/bash

FILE="/tmp/test.mp4"
TMP="/tmp/tmp.mp4"
OUT="/tmp/out.mp4"

OUT_WIDTH=720
OUT_HEIGHT=480

# Get the size of input video:
eval $(ffprobe -v error -of flat=s=_ -select_streams v:0 -show_entries stream=height,width ${FILE})
IN_WIDTH=${streams_stream_0_width}
IN_HEIGHT=${streams_stream_0_height}

# Get the difference between actual and desired size
W_DIFF=$[ ${OUT_WIDTH} - ${IN_WIDTH} ]
H_DIFF=$[ ${OUT_HEIGHT} - ${IN_HEIGHT} ]

# Let's take the shorter side, so the video will be at least as big
# as the desired size:
CROP_SIDE="n"
if [ ${W_DIFF} -lt ${H_DIFF} ] ; then
  SCALE="-2:${OUT_HEIGHT}"
  CROP_SIDE="w"
else
  SCALE="${OUT_WIDTH}:-2"
  CROP_SIDE="h"
fi

# Then perform a first resizing
ffmpeg -i ${FILE} -vf scale=${SCALE} ${TMP}

# Now get the temporary video size
eval $(ffprobe -v error -of flat=s=_ -select_streams v:0 -show_entries stream=height,width ${TMP})
IN_WIDTH=${streams_stream_0_width}
IN_HEIGHT=${streams_stream_0_height}

# Calculate how much we should crop
if [ "z${CROP_SIDE}" = "zh" ] ; then
  DIFF=$[ ${IN_HEIGHT} - ${OUT_HEIGHT} ]
  CROP="in_w:in_h-${DIFF}"
elif [ "z${CROP_SIDE}" = "zw" ] ; then
  DIFF=$[ ${IN_WIDTH} - ${OUT_WIDTH} ]
  CROP="in_w-${DIFF}:in_h"
fi

# Then crop...
ffmpeg -i ${TMP} -filter:v "crop=${CROP}" ${OUT}

Solution 2

ffmpeg -i input.file -vf "scale=(iw*sar)*max(720/(iw*sar)\,480/ih):ih*max(720/(iw*sar)\,480/ih), crop=720:480" -c:v mpeg4 -vtag XVID -q:v 4 -c:a libmp3lame -q:a 4 output.avi

Replace "input.file" with the name of your input file.

Solution 3

If you want to crop and scale in one go, you can make a crop-then-scale filterchain like so:

ffmpeg -i SomeInput.mp4 -vf "crop=in_h*9/16:in_h,scale=-2:400" -t 4 SomeOutput.mp4

The above first crops a video to 16:9 portrait, then scales to 400px high x the appropriate (even number) width.

Solution 4

I'm new to ffmpeg but now have a nice little converter going in VB.NET that creates raw movies of various output formats for use in small devices that have SD card access but not enough power to decode complex video.

Because of the way it works when resizing and cropping I had to code it manually and build the parameters. Basically I need to know the resultant width and height.

I have 2 check boxes for the options :-

Preserve Aspect Ratio (X) Fill All Pixels (X)

Only 4 variables are needed as input (1) Original video width, (2) Original video height, (3) Device width, (4) Device height.

Although for VB.NET could be adapted quite easy for any language.

    Dim crop As String = "" ' will build this in ffmpeg format for -s or scale and crop

    If cbAspectRatio.Checked = False Then
        m_dest_output_w = sz._w
        m_dest_output_h = sz._h

        ' scale to w * h - simple
        crop = " -s " & m_dest_output_w & ":" & m_dest_output_h
    Else
        Dim ratio As Double = CDbl(m_video_width) / CDbl(m_video_height) ' this is aspect ratio of original unsized video

        If cbFillPixels.Checked = False Then ' shrink to fit
            If sz._w * ratio <= m_video_height Then
                m_dest_output_w = sz._w
                m_dest_output_h = sz._w / ratio
            Else
                m_dest_output_h = sz._h
                m_dest_output_w = sz._h / ratio
            End If

            ' no cropping just resizing to a non-filled area that fits

            crop = " -s " & m_dest_output_w & ":" & m_dest_output_h ' no cropping needed as letterboxed

        Else ' expand / fill --- cropping needed
            If sz._w * ratio >= m_video_height Then
                m_dest_output_w = sz._w
                m_dest_output_h = sz._w * ratio

                crop = ",crop=" & sz._w & ":" & sz._h & ":0:" & Math.Abs((m_dest_output_h - sz._h)) \ 2
            Else
                m_dest_output_h = sz._h
                m_dest_output_w = sz._h * ratio

                crop = ",crop=" & sz._w & ":" & sz._h & ":" & Math.Abs((m_dest_output_w - sz._w)) \ 2 & ":0"
            End If

            crop = " -vf scale=" & m_dest_output_w & ":" & m_dest_output_h & crop

            m_dest_output_w = sz._w
            m_dest_output_h = sz._h
        End If
    End If
Share:
56,673

Related videos on Youtube

Andy Jeffries
Author by

Andy Jeffries

Ruby on Rails/RubyMotion developer, Taekwondo blackbelt, father, husband - my life-story in a line...

Updated on September 18, 2022

Comments

  • Andy Jeffries
    Andy Jeffries over 1 year

    I've tried to figure this out myself, but the myriad of options just baffles me.

    I want to use ideally either ffmpeg or mencoder (or something else, but those two I know I have working) to convert any incoming video to a fixed screen size.

    If the video is wider or too short for it, then centre crop the video. If it's then not the right size, the resize up or down to make it exactly the fixed screen size.

    The exact final thing I need is 720x480 in a XVid AVI with an MP3 audio track.

    I've found lots of pages showing how to resize to a maximum resolution, but I need the video to be exactly that resolution (with extra parts cropped off, no black bars).

    Can anyone tell me the command line to run - or at least get me some/most of the way there? If it needs to be multiple command lines (run X to get the resolution, do this calculation and then run Y with the output of that calculation) I can script that.

    • Andy Jeffries
      Andy Jeffries about 9 years
      Apparently the video device works with Mpeg4 as well as XVid AVI, if that's easier/better.
  • Andy Jeffries
    Andy Jeffries about 9 years
    I had this funny feeling that it'd miss a scenario, but I've tried it with loads of video files and it seems to correctly crop and resize the videos to the absolute correct size - well done!
  • cleong
    cleong over 7 years
    This should be the top answer as it doesn't require a call to ffprobe. Note the backslash comma sequences in the command. You'll need to escape the backslashes if you're placing the command in a C/JavaScript/PHP string.
  • David Cook
    David Cook almost 7 years
    Nice, Brian. The video-filter portion is exactly what I needed, too. (i.e. to convert old 4:3 videos to 16:9)!
  • Nick
    Nick over 6 years
    Much cleaner that the script above. Note if you don't want XVID/MP3 you can just change the tags after the second quotation mark to something like -c:a copy scaled_result.mp4
  • Sutra
    Sutra about 6 years
    looks like there is some wrong with CROP_SIDE decision.
  • Sutra
    Sutra about 6 years
    ` # Get the rate between actual and desired size w_rate=echo "scale=3; ${in_width}/${out_width}*1000"| bc | awk '{printf "%d", $0}' h_rate=echo "scale=3; ${in_height}/${out_height}*1000"| bc | awk '{printf "%d", $0}' # Let's take the shorter side, so the video will be at least as big # as the desired size, and crop the longer side: crop_side='n' if [ ${w_rate} -gt ${h_rate} ]; then scale="-2:${out_height}" crop_side='w' else scale="${out_width}:-2" crop_side='h' fi `
  • Salem F
    Salem F almost 4 years
    can you explain tom me what's sar inside iw*sar
  • Brian
    Brian almost 4 years
    SAR = sample aspect ratio. It is the ratio of each pixel's height to its width.
  • hellotli
    hellotli over 3 years
    It seems that the original HEVC video would be converted to H264 codec, just wondering if maybe we could keep the original HEVC format? Or we just need to add kind of -c:v libx265 -c:a copy -x265-params crf=28 to make it explicit?
  • Brian
    Brian over 3 years
    Yes. You can change the codec parameters to suit your need. This answer fills the request of the OP for XVid AVI with an MP3
  • willnode
    willnode over 2 years
    (In my case) to crop a video to 16:9 landscape at 1080p, ffmpeg -i in.mp4 -vf "crop=in_w:in_w*9/16,scale=-2:1080" out.mp4