3

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.

  • 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
3

It's relatively simple to put two 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
| improve this answer | |
  • 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
1

@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:

target_size=$(( 25 * 1000 * 1000 * 8 )) # 25MB in bits
length=`ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 input.mp4`
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 input.mp4 -b:v $video_bitrate -maxrate:v $video_bitrate -bufsize:v $(( $target_size / 20 )) -b:a $audio_bitrate output.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.

| improve this answer | |

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.