How do I reduze the size of a video to a target size?

1,597

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

file=input.mp4
target_size=$(( 25 * 1000 * 1000 * 8 )) # 25MB 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 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.

Solution 2

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

Solution 3

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
Share:
1,597

Related videos on Youtube

Traveler
Author by

Traveler

Updated on September 18, 2022

Comments

  • Traveler
    Traveler almost 2 years

    Is it possible to disable creating logback log files?

    When I set level to root or logger elements to OFF, it will not log but the file specified in appender is still created.

    Is there an option to disable creating this file? (other than deleting the config xml file)

    Even if no logger (or root) links to the appender, the log file from appender is still created.

    Thank you.

    =========================================================================== Edit. I'm attaching my config:

    <?xml version="1.0" encoding="UTF-8"?>
    
    <configuration debug="false">
    
      <variable name="LOG_LEVEL" value="${mylevel:-TRACE}" /> 
    
      <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>${clogdir}/mylog.log</file>
        <append>true</append>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
          <pattern>%d %p %t %c - %m%n</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
          <fileNamePattern>${clogdir}/mylog.log.%d{yyyy-MM-dd}</fileNamePattern>
        </rollingPolicy>
      </appender>  
    
      <root level="${LOG_LEVEL}">
        <appender-ref ref="FILE" />
      </root>
    
      <appender name="log2" class="ch.qos.logback.core.FileAppender">
        <file>${clogdir}/log2.log</file>
        <append>true</append>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
          <pattern>%d %p %t %c - %m%n</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
          <fileNamePattern>${clogdir}/log2.log.%d{yyyy-MM-dd}</fileNamePattern>
        </rollingPolicy>
      </appender>
    
      <logger name="log2" level="TRACE">
       <appender-ref ref="log2" />
      </logger>
    
    </configuration> 
    
    • Gyan
      Gyan about 5 years
      Not possible with ffmpeg.
    • Philip Couling
      Philip Couling about 5 years
      @Kiwy it's very rare for a question referencing another to be an exact duplicate of the question they're referencing.
    • frostschutz
      frostschutz about 5 years
      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.
  • Traveler
    Traveler almost 6 years
    I have attached my log file. The root logger should be created only when I specify it. Now I can only tell it not to append log lines by setting LOG_LEVEL to OFF. But the file myLog.log is still created. Even if I change <root level="${LOG_LEVEL}"> <appender-ref ref="FILE" /></root> to <root level="${LOG_LEVEL}"><appender-ref ref="NONEXISTANT" /></root>. The log2 is created every time, that's ok.
  • glytching
    glytching almost 6 years
    Apologies, I have updated my answer, turns out that just including the appender in the config (regardless of whether that appender is even referenced by a logger) is enough to cause the appender's file to be created.
  • Traveler
    Traveler almost 6 years
    Thank you for the answer. Is it possible to reference other xml in the config? I'm thinking about the possibility to define the appender in other xml file and then reference it only when needed..
  • glytching
    glytching almost 6 years
    You could perhaps use conditional configuration to enable/disable your file appender. In this way you could have one logback.xml file but with two different behaviours depending on a condition such as the value of a JVM system parameter e.g. -Denable.file.output=true|false
  • Traveler
    Traveler almost 6 years
    Works like a charm. Thank you very much :)
  • klutt
    klutt about 5 years
    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
  • Philip Couling
    Philip Couling about 5 years
    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.
  • klutt
    klutt about 5 years
    Did that. The size of the file became 28MB. 3MB too large.
  • klutt
    klutt almost 3 years
    This is amazing. I have used this as a simple cut and paste several times now. Works flawlessly.