-
Notifications
You must be signed in to change notification settings - Fork 0
/
shrink
executable file
·123 lines (113 loc) · 4.34 KB
/
shrink
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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#!/bin/bash
source /home/fabian/d/programs/bash_scripts/sane
# This script forces a video below a certain size, with the best quality possible for that size. It also converts to MP4, because why not.
# Exit value is 3 on FFMPEG error, 2 if a so far unsupported file type was provided (currently only certain videos work), 1 if the file couldn't be made small enough, otherwise 0 indicates success and then the last line of output is the path/file that worked. To get the filename in a script without letting "tail" overwrite the exit value, use one of these:
# temp=$(resize.sh); if(($?==0)); then echo $temp | tail -n 1; fi
# if temp=$(resize.sh); then echo $temp | tail -n 1; fi
# Effective working directory will be the one with the input file in it.
### INITIALISATION AND VALIDATION
## interactive or command line parameters
if [[ -r "$1" && ( "$2" == "d" || "$2" == "t" || "$2" == "g" || "$2" == "b" || "$2" =~ ^[0-9]+$ )]]; then
# input file path/name
in="$1"
# maximum file size allowed, examples:
if [[ "$2" == "d" ]]; then
# Discord: 25MiB=25*1024² Bytes
limit=26214400
elif [[ "$2" == "t" ]]; then
# Telegram: 2000 MiB=2000*1024² Bytes
limit=2097152000
elif [[ "$2" == "g" ]]; then
# GermanZero Sharepoint: 2 SI GB=2*1000³ Bytes
limit=2000000000
elif [[ "$2" == "b" ]]; then
# 32bit systems: <2GiB→2*1024³-1 Bytes
limit=2147483647
else
limit=$2
fi
else
echo -n "Input file: "
while true; do
read in
if [[ -r "$1" ]]; then break; fi
echo "Invalid or unreadable, try again or abort with Ctrl+Esc."
done
echo -n "File size limit in bytes: "
while true; do
read limit
if [[ "$2" =~ ^[0-9]+$ ]]; then break; fi
echo "Not an integer, try again or abort with Ctrl+Esc."
done
fi
# If this is set to "true", the files that resulted from all the attempts except for the best one are deleted. If set to "false", they are kept. This does not guarantee that any specific file will exist (but it is predictable, for example if CRF 18 is the best, then the files with CRF 51, 25, 12, 18, 15, 16 and 17 will exist).
if [[ "$3" == "false" ]]; then
delete_intermediate_results=false
else
# default is true
delete_intermediate_results=true
fi
# start values, crf=51 is worst possible
good=0
bad=51
curr=51
# filename without extension
name="${in%.*}"
# file extension
ext="${in#*.}"
# reason: CRF 0..51 range apparently only apples to h264
if ! ffprobe "$in" 2>&1 | grep -q h264; then
echo "Not a supported file! Currently only h264 (usually .mp4) video is supported. ffprobe output:"
ffprobe "$in"
exit 2
fi
output(){
echo "This file succeeded ($(stat --printf="%s" "$out")<$limit):"
echo "$out"
exit
}
in_size=$(stat --printf="%s" "$in")
if((in_size<limit)); then
echo "Input is already below limit!"
out="$in"
output
fi
### MAIN PART
echo "Input file size: $in_size"
while true; do
out="$name""_$curr.$ext"
echo -n "$(date +"%H:%M:%S") testing crf=$curr, filename: $name""_$curr.$ext"
#############################################################
# This is where the script will spend 99% of its time! #
ffmpeg -nostdin -i "$in" -crf $curr -c:s copy -v 16 "$out" #
#############################################################
if(($?!=0)); then
echo "\nFFMPEG error!"
exit 3
fi
size=$(stat --printf="%s" "$out")
echo -n ", resulting size: $size"
if((size>limit)); then
if((curr==51)); then
echo ">$limit, even with CRF=51"
if [[ $delete_intermediate_results == true ]]; then rm "$out"; fi
exit 1
fi
echo ">$limit"
# If the current result is above the limit, it gets deleted. Since some valid result should already exist with CRF=51 or whatever else happened later (except if the script fails completely), there will always be a file left.
if [[ $delete_intermediate_results == true ]]; then rm "$name""_$curr.$ext"; fi
good=$curr
else
echo "≤$limit"
# If the current result is below the limit, then the previous worse/smaller result is no longer needed.
if [[ $delete_intermediate_results == true && $curr != 51 ]]; then rm "$name""_$bad.$ext"; fi
bad=$curr
fi
# Stop searching if narrowed down to exact number, pick best value that is "worse" than the limit, which luckily already is the current value of $bad.
# CRF 0 will never be picked. Normally it shouldn't make the file smaller anyway and it leads to issues in some programs.
if((bad-good<2)); then
out="$name""_$bad.$ext"
output
fi
curr=$(((good+bad)/2))
done