10

I want to reduce the size of a video to be able to send it via email and such. I looked at this question: How can I reduce a video's size with ffmpeg? where I got good advice on how to reduce it. Problem is that I need to manually calculate the bitrate. And also when doing this I had to finish of with manual trial and error.

Is there any good way to use ffmpeg (or another tool) to reduce the size of a video to a target size?

Note due to a comment from frostschutz:

Worrying too much about size and going for a fixed filesize regardless of video length / resolution / framerate / content will not give satisfactory results most of the time... Upload it somewhere, email the link. If you must encode it, set a quality level, leave the bitrate dynamic. If it's obvious that size will not be met, cancel the encode and adapt quality level accordingly. Rinse and repeat.

Good advice in general, but it does not suit my usecase. Uploading to an external link is not an option due to technical limitations, one of them being that the receiver does not have http access.

I could also clarify that I'm NOT looking for exactly the size I'm specifying. It's enough if it is reasonably close to what I want (maybe up to 5 or 10% lower or so) but it should be guaranteed that it will not exceed my target limit.

thanasisp
  • 8,122
klutt
  • 544
  • Not possible with ffmpeg. – Gyan May 23 '19 at 10:35
  • @Kiwy it's very rare for a question referencing another to be an exact duplicate of the question they're referencing. – Philip Couling May 23 '19 at 11:20
  • Worrying too much about size and going for a fixed filesize regardless of video length / resolution / framerate / content will not give satisfactory results most of the time... Upload it somewhere, email the link. If you must encode it, set a quality level, leave the bitrate dynamic. If it's obvious that size will not be met, cancel the encode and adapt quality level accordingly. Rinse and repeat. – frostschutz May 23 '19 at 21:25

4 Answers4

12

@philip-couling's answer is close but missing several pieces:

  • The desired size in MB needs to be multiplied by 8 to get bit rate
  • For bit rates, 1Mb/s = 1000 kb/s = 1000000 b/s; the multipliers aren't 1024 (there is another prefix, KiB/s, which is 1024 B/s, but ffmpeg doesn't appear to use that)
  • Truncating or rounding down a non-integer length (instead of using it precisely or rounding up) will result in the file size slightly exceeding target size
  • The -b flag specifies the average bit rate: in practice the encoder will let the bit rate jump around a bit, and the result could overshoot your target size. You can however specify a max bit rate by using -maxrate and -bufsize

For my use-case I also wanted a hardcoded audio bitrate and just adjust video accordingly (this seems safer too, I'm not 100% certain ffmpeg's -b flag specifies bitrate for video and audio streams together or not).

Taking all these into account and limiting to strictly 25MB:

file="input.mp4"
target_size_mb=25 # 25MB target size
target_size=$(( $target_size_mb * 1000 * 1000 * 8 )) # target size in bits
length=`ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$file"`
length_round_up=$(( ${length%.*} + 1 ))
total_bitrate=$(( $target_size / $length_round_up ))
audio_bitrate=$(( 128 * 1000 )) # 128k bit rate
video_bitrate=$(( $total_bitrate - $audio_bitrate ))
ffmpeg -i "$file" -b:v $video_bitrate -maxrate:v $video_bitrate -bufsize:v $(( $target_size / 20 )) -b:a $audio_bitrate "${file}-${target_size_mb}mb.mp4"

Note that when -maxrate and -bufsize limit the max bit rate, by necessity the average bit rate will be lower, so the video will undershoot the target size by as much as 5-10% in my tests (on a 20s video at various target sizes). The value of -bufsize is important, and the calculated value used above (based on target size) is my best guess. Too small and it will vastly lower quality and undershoot target size by like 50%, but too large and I think it could potentially overshoot target size?

To give the encoder more flexibility if you don't have a strict maximum file size, removing -maxrate and -bufsize will result in better quality, but can cause the video to overshoot the target size by 5% in my tests. More info in the docs, where you will see this warning:

Note: Constraining the bitrate might result in low quality output if the video is hard to encode. In most cases (such as storing a file for archival), letting the encoder choose the proper bitrate is the constant quality or CRF-based encoding.


You can use the following code to declare a reusable bash function:

ffmpeg_resize () {
    file=$1
    target_size_mb=$2  # target size in MB
    target_size=$(( $target_size_mb * 1000 * 1000 * 8 )) # target size in bits
    length=`ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$file"`
    length_round_up=$(( ${length%.*} + 1 ))
    total_bitrate=$(( $target_size / $length_round_up ))
    audio_bitrate=$(( 128 * 1000 )) # 128k bit rate
    video_bitrate=$(( $total_bitrate - $audio_bitrate ))
    ffmpeg -i "$file" -b:v $video_bitrate -maxrate:v $video_bitrate -bufsize:v $(( $target_size / 20 )) -b:a $audio_bitrate "${file}-${target_size_mb}mb.mp4"
}

ffmpeg_resize file1.mp4 25 # resize `file1.mp4` to 25 MB
ffmpeg_resize file2.mp4 64 # resize `file2.mp4` to 64 MB
tobek
  • 388
  • 4
  • 11
  • 2
    This is amazing. I have used this as a simple cut and paste several times now. Works flawlessly. – klutt Sep 01 '21 at 15:50
  • I used this answer (with some modifications) to convert a file to a DVD format (for Burn on Mac) just under 4.7GB (4.5GB) with success! — https://pastebin.com/HcTpGVd8 – aubreypwd Dec 30 '22 at 17:03
2

Not enough reputation to comment. Adjusted @tobek's code above to work as a windows/dos batch file.

@echo off
setlocal

if "%~1" == "" ( echo Usage: echo. echo %0 filename.mp4 echo or echo %0 filename.mp4 25 echo (25 = 25MB^) goto end )

set target_mb=%2 if "%~2" == "" ( rem # default set target_mb=25 )

set file=%1 set /a target_size=%target_mb% * 1000 * 1000 * 8 for /f %%c in ('ffprobe -v error -show_entries format^=duration -of default^=noprint_wrappers^=1:nokey^=1 "%file%"') do set length=%%c

for /f "tokens=1 delims=." %%n in ('echo %length%') do set length_round_up=%%n set /a length_round_up=%length_round_up% + 1

rem # 128k bit rate set /a audio_bitrate=128 * 1000 set /a total_bitrate=%target_size% / %length_round_up% set /a video_bitrate=%total_bitrate% - %audio_bitrate% set /a bufsize=%target_size% / 20

for %%f in ("%file%") do ( set filedrive=%%~df set filepath=%%~pf set filename=%%~nf set fileextension=%%~xf )

set file_output=%filedrive%%filepath%%filename%-out%fileextension%

ffmpeg -i "%file%" -b:v %video_bitrate% -maxrate:v %video_bitrate% -bufsize:v %bufsize% -b:a %audio_bitrate% "%file_output%" :end

lysp
  • 21
1

Disclaimer: This does not seem to work perfectly. It becomes slightly to large.

It's relatively simple to put two other answers together here:

Assuming you want a 10MB file (10485760 bytes) you could use ffprobe to find the duration and get the shell to perform the calculation.

Just be careful because ffprobe will report decimal places which will trip up the shell arithmetic. I've used ${length%.*} to strip off the decimal places:

size=10485760 
length=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 input.mp4)
ffmpeg -i input.mp4 -b $(( $size / ${length%.*} )) output.mp4
AdminBee
  • 22,803
  • I tried this with size=25000000 but the file ended up at 5MB. If it were off by a factor 8, it could be explained with bits/bytes, but now it's off with a factor 5 – klutt May 23 '19 at 11:46
  • Hmmm, that's a little odd. Do try with a bits to by conversion anyway and see what happens. 25MB is 209715200 bits. (25 1024 1024 * 8). Also echo $(( $size / ${length%.*} )) just to check the bitrate you're asking for. – Philip Couling May 23 '19 at 12:29
  • 1
    Did that. The size of the file became 28MB. 3MB too large. – klutt May 23 '19 at 12:58
0

Complement and small correction of the most voted answer by @tobek

Megabytes (MiB) equals 8,388.608 k = (1024^2) / (1000/8) Source: ffmpeg documentation.

Example of video bitrate calculation for 2 hours with 256kbits audio, aiming to reach 2000 MiB (Telegram upload limit) 8,388.608 * 2000 / 7200 - 256 = 2074k

So when assembling the command, the video_bitrate flag would look like this: -b:v 2074K

the_RR
  • 109