26

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.

1
  • Apparently the video device works with Mpeg4 as well as XVid AVI, if that's easier/better. – Andy Jeffries Mar 16 '15 at 8:57
22
+100

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}
3
  • 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! – Andy Jeffries Mar 24 '15 at 18:16
  • looks like there is some wrong with CROP_SIDE decision. – Sutra Apr 3 '18 at 8:59
  • ` # 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 ` – Sutra Apr 3 '18 at 9:49
27
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.

8
  • 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. – cleong Jan 23 '17 at 11:35
  • Nice, Brian. The video-filter portion is exactly what I needed, too. (i.e. to convert old 4:3 videos to 16:9)! – Dave Aug 24 '17 at 12:59
  • 1
    genius! all done in one. – Soli Dec 1 '17 at 4:22
  • 3
    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 – Nick Jan 22 '18 at 0:19
  • can you explain tom me what's sar inside iw*sar – Salem F Aug 18 '20 at 17:54
14

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.

1

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.