ffmpeg video compression / specific file size

42,565

Solution 1

if you are targeting a certain output file size the best way is to use H.264 and Two-Pass encoding.

There is a great example here but it's too large to copy-paste: https://trac.ffmpeg.org/wiki/Encode/H.264#twopass

You calculate your target bitrate using bitrate = target size / duration and you launch ffmpeg two times: one pass analyzes the media and the second does the actual encoding:

ffmpeg -y -i input -c:v libx264 -preset medium -b:v 555k -pass 1 -c:a libfdk_aac -b:a 128k -f mp4 /dev/null && \
ffmpeg -i input -c:v libx264 -preset medium -b:v 555k -pass 2 -c:a libfdk_aac -b:a 128k output.mp4

Edit: H.265 (HEVC) is even better at compression (50% of H.264 size in some cases) but support is not yet widespread so stick with H.264 for now.

Solution 2

Inspired by Hashbrown's answer. This version keeps the original audio quality, and resizes to the target size.

NEW

  1. Renamed variables for readability and consistency.
  2. Removed dependency on grep (-P switch not implemented in OSX grep)
  3. The script now exits with a helpful message if target size would be too small.

Script

#!/bin/bash
#
# Re-encode a video to a target size in MB.
# Example:
#    ./this_script.sh video.mp4 15

T_SIZE="$2" # target size in MB
T_FILE="${1%.*}-$2MB.mp4" # filename out

# Original duration in seconds
O_DUR=$(\
    ffprobe \
    -v error \
    -show_entries format=duration \
    -of csv=p=0 "$1")

# Original audio rate
O_ARATE=$(\
    ffprobe \
    -v error \
    -select_streams a:0 \
    -show_entries stream=bit_rate \
    -of csv=p=0 "$1")

# Original audio rate in KiB/s
O_ARATE=$(\
    awk \
    -v arate="$O_ARATE" \
    'BEGIN { printf "%.0f", (arate / 1024) }')

# Target size is required to be less than the size of the original audio stream
T_MINSIZE=$(\
    awk \
    -v arate="$O_ARATE" \
    -v duration="$O_DUR" \
    'BEGIN { printf "%.2f", ( (arate * duration) / 8192 ) }')

# Equals 1 if target size is ok, 0 otherwise
IS_MINSIZE=$(\
    awk \
    -v size="$T_SIZE" \
    -v minsize="$T_MINSIZE" \
    'BEGIN { print (minsize < size) }')

# Give useful information if size is too small
if [[ $IS_MINSIZE -eq 0 ]]; then
    printf "%s\n" "Target size ${T_SIZE}MB is too small!" >&2
    printf "%s %s\n" "Try values larger than" "${T_MINSIZE}MB" >&2
    exit 1
fi

# Set target audio bitrate
T_ARATE=$O_ARATE


# Calculate target video rate - MB -> KiB/s
T_VRATE=$(\
    awk \
    -v size="$T_SIZE" \
    -v duration="$O_DUR" \
    -v audio_rate="$O_ARATE" \
    'BEGIN { print  ( ( size * 8192.0 ) / ( 1.048576 * duration ) - audio_rate) }')

# Perform the conversion
ffmpeg \
    -y \
    -i "$1" \
    -c:v libx264 \
    -b:v "$T_VRATE"k \
    -pass 1 \
    -an \
    -f mp4 \
    /dev/null \
&& \
ffmpeg \
    -i "$1" \
    -c:v libx264 \
    -b:v "$T_VRATE"k \
    -pass 2 \
    -c:a aac \
    -b:a "$T_ARATE"k \
    $T_FILE

NOTES

  • See comments for a possible Windows version.

SOURCES

Hashbrown's answer (in this thread)

Two Pass method

Solution 3

Here's a way to do it automatically with a bash script
Just call like ./script.sh file.mp4 15 for 15mB

bitrate="$(awk "BEGIN {print int($2 * 1024 * 1024 * 8 / $(ffprobe \
    -v error \
    -show_entries format=duration \
    -of default=noprint_wrappers=1:nokey=1 \
    "$1" \
) / 1000)}")k"
ffmpeg \
    -y \
    -i "$1" \
    -c:v libx264 \
    -preset medium \
    -b:v $bitrate \
    -pass 1 \
    -an \
    -f mp4 \
    /dev/null \
&& \
ffmpeg \
    -i "$1" \
    -c:v libx264 \
    -preset medium \
    -b:v $bitrate \
    -pass 2 \
    -an \
    "${1%.*}-$2mB.mp4"

NB I'm cutting audio out

Share:
42,565
justin.esders
Author by

justin.esders

Updated on December 23, 2021

Comments

  • justin.esders
    justin.esders over 2 years

    Currently I have 80mb movies that I want to use ffmpeg to convert down to say about 10mb or 15mb. I know there will be a quality loss but they will need to have sound. Is there a way to either specify a file size or higher compression than what I have done previously

    ffmpeg -i movie.mp4 -b 2255k -s 1280x720 movie.hd.ogv
    

    They are currently about 25mb a piece