Skip to content

Use ffmpeg to compress video with multiple tracks to specific size

I had some multi-track videos I needed to compress, and I found this awesome script for using ffmpeg to compress videos to specific sizes:
https://stackoverflow.com/questions/29082422/ffmpeg-video-compression-specific-file-size/61146975#61146975

And it works great!  But, only includes the first track.  So I made a few slight changes to make it include multiple audio tracks, and factor them in to the total size of the end result.

I added this part to calculate the total number of audio tracks.  Courtesy of this answer.

1
2
3
4
5
6
7
#number of audio streams
O_ACOUNT=$(\
 ffprobe \
 -v error \
 -select_streams a \
 -show_entries stream=index \
 -of csv=p=0 "$1" | wc -w)

And, then multiplied the audio_rate by the number of tracks

1
2
3
4
5
6
7
8
# Calculate target video rate - MB -> KiB/s
T_VRATE=$(\
 awk \
 -v size="$T_SIZE" \
 -v duration="$O_DUR" \
 -v audio_rate="$O_ARATE" \
 -v audio_count="$O_ACOUNT" \
 'BEGIN { print ( ( size * 8192.0 ) / ( 1.048576 * duration ) - (audio_rate * audio_count)) }')

And I added -map 0 to the export at the end so it will capture all the audio streams

1
2
3
4
5
6
7
8
9
ffmpeg \
 -i "$1" \
 -c:v libx264 \
 -b:v "$T_VRATE"k \
 -pass 2 \
 -c:a aac \
 -map 0 \
 -b:a "$T_ARATE"k \
 $T_FILE

So the final script ended up as:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#!/bin/bash
#
# Re-encode a video to a target size in MB.
# Example:
# ./this_script.sh video.mp4 15
#
# Filename CANNOT contain any spaces
 
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) }')
 
#number of audio streams
O_ACOUNT=$(\
 ffprobe \
 -v error \
 -select_streams a \
 -show_entries stream=index \
 -of csv=p=0 "$1" | wc -w)
 
# 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" \
 -v audio_count="$O_ACOUNT" \
 'BEGIN { print ( ( size * 8192.0 ) / ( 1.048576 * duration ) - (audio_rate * audio_count)) }')
 
# 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 \
 -map 0 \
 -b:a "$T_ARATE"k \
 $T_FILE
Published inOtherScripting

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *