aboutsummaryrefslogtreecommitdiffstats
path: root/.config/init/funcs/make-gif
blob: 1864e635c6359cd6e52df0686b0d79634faef219 (plain)
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
#!/usr/bin/env zsh
# export a clip from a video as a gif

local callstr="$0"
local hasgsic=$(whence gifsicle)

usage() {
	[[ "$1" != "" ]] && echo -e "\e[1;31merror:\e[0m $1\n"
	echo "Usage: $callstr [OPTIONS...] <infile> <outfile>"
	echo ""
	echo "           description  option     default val"
	echo "            start time  -s <time>  00:00:00"
	echo "     length in seconds  -t <num>   full length"
	echo "               gif fps  -f <num>   10"
	echo "       gif pixel width  -w <num>   480"
	echo "         use subtitles  -b"
	echo "    use subtitle track  -n <int>   0"
	[[ $hasgsic ]] && echo "optimise with gifsicle  -g"
	echo "       print this help  -h"
	exit 1
}

local start="00:00:00"
local length=""
local fps=10
local width=480
local subs=""
local strack=0
local gsic=""

local timepat='^(([0-9][0-9]:){1,2}[0-9][0-9]|[0-9]+)(\.[0-9]+){0,1}$'
local numpat='^[1-9][0-9]*(\.[0-9]+){0,1}$'
local intpat='^[1-9][0-9]*$'
local zintpat='^[0-9]+$'

# tmp var used to old '-t' if length is used
local t=""

# really annoying, but no other good way to do this
if [[ $hasgsic ]]; then
	while getopts :s:t:f:w:bhn:g opt; do
		case "$opt" in
			s) 
				[[ ! $(echo $OPTARG | grep -oE "$timepat") ]] \
					&& usage "malformed start timestamp"
				start="$OPTARG"
				;;
			t)
				[[ ! $(echo $OPTARG | grep -oE "$numpat") ]] \
					&& usage "length must be a positive rational number"
				length=$OPTARG
				t="-t"
				;;
			f)
				[[ ! $(echo $OPTARG | grep -oE "$numpat") ]] \
					&& usage "fps must be a positive rational number"
				fps=$OPTARG
				;;
			w)
				[[ ! $(echo $OPTARG | grep -oE "$intpat") ]] \
					&& usage "width must be a positive integer"
				width=$OPTARG
				;;
			b) subs=true ;;
			n)
				[[ ! $(echo $OPTARG | grep -oE "$zintpat") ]] \
					&& usage "sub track index must be a non-negative integer"
				strack=$OPTARG
				;;
			g) gsic=true ;;
			h) usage ;;
			[?]) usage "unrecognised option" ;;
		esac
	done
else
	while getopts :s:t:f:w:bhn: opt; do
		case "$opt" in
			s) 
				[[ ! $(echo $OPTARG | grep -oE "$timepat") ]] \
					&& usage "malformed start timestamp"
				start="$OPTARG"
				;;
			t)
				[[ ! $(echo $OPTARG | grep -oE "$numpat") ]] \
					&& usage "length must be a positive rational number"
				length=$OPTARG
				t="-t"
				;;
			f)
				[[ ! $(echo $OPTARG | grep -oE "$numpat") ]] \
					&& usage "fps must be a positive rational number"
				fps=$OPTARG
				;;
			w)
				[[ ! $(echo $OPTARG | grep -oE "$intpat") ]] \
					&& usage "width must be a positive integer"
				width=$OPTARG
				;;
			b) subs=true ;;
			n)
				[[ ! $(echo $OPTARG | grep -oE "$zintpat") ]] \
					&& usage "sub track index must be a non-negative integer"
				strack=$OPTARG
				;;
			h) usage ;;
			[?]) usage "unrecognised option" ;;
		esac
	done
fi
shift $OPTIND-1

[[ ${#@} -gt 2 ]] && usage "trailing arguments detected"

[[ ${#@} -lt 2 ]] && usage "no output file specified"

[[ "${2:e}" != "gif" ]] && usage "output file must have a .gif file extension"

[[ ! -f "$1" ]] && usage "input file not found"

local paltmp="make-gif"
while [[ -f "${paltmp}-palette.png" ]] || [[ -f "${paltmp}-in" ]]; do
	paltmp="${paltmp}-1"
done

ln -s "$1" "${paltmp}-in"

local fferr

if [[ $subs ]]; then
	echo "pass 1..."
	ffmpeg -loglevel 16 -y -ss $start $t $length -i "$1" -copyts \
		-vf "subtitles=${paltmp}-in:si=$strack,fps=$fps,scale=$width:-1:flags=lanczos,palettegen" \
		${paltmp}-palette.png
	[[ $? -ne 0 ]] && fferr=true
	[[ ! $fferr ]] && echo "pass 2..." && ffmpeg -loglevel 24 \
		-ss $start $t $length -i "$1" -i ${paltmp}-palette.png \
		-copyts -filter_complex \
		"subtitles=${paltmp}-in:si=$strack,fps=$fps,scale=$width:-1:flags=lanczos[x];[x][1:v]paletteuse" \
		"$2"
else
	echo "pass 1..."
	ffmpeg -loglevel 16 -y -ss "$start" $t $length -i "$1" \
		-vf "fps=$fps,scale=$width:-1:flags=lanczos,palettegen" \
		${paltmp}-palette.png
	[[ $? -ne 0 ]] && fferr=true
	[[ ! $fferr ]] && echo "pass 2..." && ffmpeg -loglevel 24 \
		-ss $start $t $length -i "$1" -i ${paltmp}-palette.png -filter_complex \
		"fps=$fps,scale=$width:-1:flags=lanczos[x];[x][1:v]paletteuse" \
		"$2"
fi

rm -f ${paltmp}-palette.png
rm -f ${paltmp}-in

if [[ -f "$2" ]] && [[ ! $fferr ]]; then
	if [[ $gsic ]]; then
		local gsictmp="$2.out"
		while [[ -f "$gsictmp" ]]; do
			gsictmp="$gsictmp.out"
		done
		echo "optimising..."
		gifsicle -O3 -i "$2" -o "$gsictmp"
		[[ $? -eq 0 ]] && rm -f "$2" && mv "$gsictmp" "$2"
	fi
fi