-
Notifications
You must be signed in to change notification settings - Fork 5
/
gifv.sh
executable file
·207 lines (168 loc) · 5.34 KB
/
gifv.sh
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
#!/usr/bin/env bash
# Set global variables
PROGNAME=$(basename "$0")
VERSION='1.1.5'
print_help() {
cat <<EOF
Usage: $PROGNAME [options] input-file
Version: $VERSION
Convert GIFs and videos into GIF-like videos
Options: (all optional)
-c CROP The x and y crops, from the top left of the image (e.g. 640:480)
-d DIRECTION Directon (normal, reverse, alternate) [default: normal]
-l LOOP Play the video N times [default: 1]
-o OUTPUT The basename of the file to be output. The default is the
basename of the input file.
-r FPS Output at this (frame)rate.
-s SPEED Output using this speed modifier. The default is 1 (equal speed).
-t DURATION Speed or slow video to a target duration
-M Write "optimized=true" to video metadata
-O OPTIMIZE Change the compression level used (1-9), with 1 being the
fastest, with less compression, and 9 being the slowest, with
optimal compression. The default compression level is 6.
-p SCALE Rescale the output (e.g. 320:240)
-x Remove the original file
Example:
gifv -c 240:80 -o gifv.mp4 -x video.mov
EOF
exit $1
}
##
# Check for a dependency
#
# @param 1 Command to check
##
dependency() {
hash "$1" &>/dev/null || error "$1 must be installed"
}
##
# Join a list with a seperator
#
# @param 1 Seperator
# @param 2+ Items to join
##
join_by() { local d=$1; shift; echo -n "$1"; shift; printf "%s" "${@/#/$d}"; }
################################################################################
# Check dependencies
dependency ffmpeg
# Initialize variables
levels=(ultrafast superfast veryfast faster fast medium slow slower veryslow)
level=6
tempdir=/tmp/
loop=1
OPTERR=0
while getopts "c:d:l:o:p:r:s:t:O:Mhx" opt; do
case $opt in
c) crop=$OPTARG;;
d) direction_opt=$OPTARG;;
h) print_help 0;;
l) loop=$OPTARG;;
o) output_file=$OPTARG;;
p) scale=$OPTARG;;
r) fps=$OPTARG;;
s) speed=$OPTARG;;
t) target_duration=$OPTARG;;
M) add_metadata=1;;
O) level=$OPTARG;;
x) remove_input=1;;
*) print_help 1;;
esac
done
shift $(( OPTIND - 1 ))
# Store input file
input_file="$1"
# Print help, if no input file
[ -z "$input_file" ] && print_help 1
# Automatically set output file, if not defined
if [ -z "$output_file" ]; then
# Strip off input file extension and add new extension
input_file_ext="${input_file##*.}"
input_file_path=$(dirname "$input_file")
output_file="$input_file_path/$(basename "$input_file" ".$input_file_ext").mp4"
# If overwriting the input file, output as temporary file,
# then move into place
if [ "$input_file" -ef "$output_file" ]; then
input_file_basename="${input_file##*/}"
tmp_output_file="$tempdir/$input_file_basename"
output_file="$tmp_output_file"
fi
fi
# Video filters (scale, crop, speed)
if [ $crop ]; then
crop="crop=${crop}:0:0"
fi
if [ $scale ]; then
scale="scale=${scale}"
fi
if [ $target_duration ]; then
source_duration=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$input_file")
# Can also set audio speed using atempo
# atempo=(1/(${target_duration}/${source_duration}))
speed="setpts=(${target_duration}/(${source_duration}*${loop}))*PTS"
elif [ $speed ]; then
# atempo=${speed}
speed="setpts=(1/${speed})*PTS"
fi
# Convert GIFs
# A fix for gifs that may not have a perfectly sized aspect ratio
if [ "$(file -b --mime-type "$input_file")" == image/gif ]; then
giffix="scale='if(eq(mod(iw,2),0),iw,iw-1)':'if(eq(mod(ih,2),0),ih,ih-1)'"
fi
# Direction
if [ "$direction_opt" == "reverse" ]; then
direction="reverse"
elif [[ "$direction_opt" == "alternate" ]]; then
filter="trim=start_frame=1,reverse,trim=start_frame=1,setpts=PTS-STARTPTS[rev];[0:v][rev]concat"
fi
# Concatenate options into a filter string
# Note: giffix must be applied after any scaling/cropping
if [ $scale ] || [ $crop ] || [ $speed ] || [ $giffix ] || [ $direction ]; then
filter="$(join_by "[v];[v]" $filter $(join_by , $scale $crop $speed $giffix $direction))"
fi
# Prepare filter string opt
if [ $filter ]; then
filter="-filter_complex [0:v]${filter}[v] -map [v]"
fi
# FPS
if [ $fps ]; then
fps="-r $fps"
fi
# Loop
if [[ $loop -gt 1 ]]; then
loop_arg="-stream_loop $(( $loop - 1 ))"
fi
# Optimization level
# 1: Fastest, worst compression
# 9: Slowest, best compression
(( $level > 9 )) && level=9 # OR err
(( $level < 1 )) && level=1 # OR err
optimize="${levels[$((level-1))]}"
# TODO: Offer further optimizations
# Constant rate factor (for better optimizations): -crf 22
# Bit rate: -b:v 1000k
# Codecs:
# libvpx: webm
# libx264: h.264
codec="-c:v libx264"
# Remove writing library metadata
bsf="-bsf:v filter_units=remove_types=6 -fflags +bitexact"
# -bitexact -map_metadata -1
# Add "optimized=true" to metadata
if [ $add_metadata ]; then
metadata="-movflags use_metadata_tags -metadata optimized=true"
fi
# Verbosity
verbosity="-loglevel error"
# Create optimized GIF-like video
ffmpeg $verbosity $loop_arg -i "$input_file" $codec $filter $fps $bsf \
-an -pix_fmt yuv420p -preset "$optimize" -movflags faststart \
$metadata "$output_file"
# If temporary output file exists, then overwrite input file
if [ -f "$tmp_output_file" ]; then
mv -f "$tmp_output_file" "$input_file"
fi
# Remove input file
if [ $remove_input ]; then
rm "$input_file"
fi
trap "rm -f $tmp_output_file" EXIT INT TERM