Commit | Line | Data |
---|---|---|
ad0ecd05 SBS |
1 | #!/usr/bin/env bash |
2 | # Desc: Combines video files into time lapse video. | |
3 | # Usage: timelapse_from_videos.sh [FILES] | |
4 | # Example: timelapse_from_videos.sh ./*.MP4 | |
5 | ||
6 | declare -ag arrayPosArgs | |
7 | declare -Ag appRollCall # Associative array for storing app status | |
8 | declare -Ag fileRollCall # Associative array for storing file status | |
9 | declare -Ag dirRollCall # Associative array for storing dir status | |
10 | ||
11 | yell() { echo "$0: $*" >&2; } # print script path and all args to stderr | |
12 | die() { yell "$*"; exit 111; } # same as yell() but non-zero exit status | |
13 | try() { "$@" || die "cannot $*"; } # runs args as command, reports args if command fails | |
14 | vbm() { | |
15 | # Description: Prints verbose message ("vbm") to stderr if opVerbose is set to "true". | |
16 | # Usage: vbm "DEBUG :verbose message here" | |
17 | # Version 0.2.0 | |
18 | # Input: arg1: string | |
19 | # vars: opVerbose | |
20 | # Output: stderr | |
21 | # Depends: bash 5.1.16, GNU-coreutils 8.30 (echo, date) | |
22 | ||
23 | if [ "$opVerbose" = "true" ]; then | |
24 | functionTime="$(date --iso-8601=ns)"; # Save current time in nano seconds. | |
25 | echo "[$functionTime]:$0:""$*" 1>&2; # Display argument text. | |
26 | fi | |
27 | ||
28 | # End function | |
29 | return 0; # Function finished. | |
30 | } # Displays message if opVerbose true | |
31 | checkapp() { | |
32 | # Desc: If arg is a command, save result in assoc array 'appRollCall' | |
33 | # Usage: checkapp arg1 arg2 arg3 ... | |
34 | # Version: 0.1.1 | |
35 | # Input: global assoc. array 'appRollCall' | |
36 | # Output: adds/updates key(value) to global assoc array 'appRollCall' | |
37 | # Depends: bash 5.0.3 | |
38 | local returnState | |
39 | ||
40 | #===Process Args=== | |
41 | for arg in "$@"; do | |
42 | if command -v "$arg" 1>/dev/null 2>&1; then # Check if arg is a valid command | |
43 | appRollCall[$arg]="true"; | |
44 | if ! [ "$returnState" = "false" ]; then returnState="true"; fi; | |
45 | else | |
46 | appRollCall[$arg]="false"; returnState="false"; | |
47 | fi; | |
48 | done; | |
49 | ||
50 | #===Determine function return code=== | |
51 | if [ "$returnState" = "true" ]; then | |
52 | return 0; | |
53 | else | |
54 | return 1; | |
55 | fi; | |
56 | } # Check that app exists | |
57 | checkfile() { | |
58 | # Desc: If arg is a file path, save result in assoc array 'fileRollCall' | |
59 | # Usage: checkfile arg1 arg2 arg3 ... | |
60 | # Version: 0.1.1 | |
61 | # Input: global assoc. array 'fileRollCall' | |
62 | # Output: adds/updates key(value) to global assoc array 'fileRollCall'; | |
63 | # Output: returns 0 if app found, 1 otherwise | |
64 | # Depends: bash 5.0.3 | |
65 | local returnState | |
66 | ||
67 | #===Process Args=== | |
68 | for arg in "$@"; do | |
69 | if [ -f "$arg" ]; then | |
70 | fileRollCall["$arg"]="true"; | |
71 | if ! [ "$returnState" = "false" ]; then returnState="true"; fi; | |
72 | else | |
73 | fileRollCall["$arg"]="false"; returnState="false"; | |
74 | fi; | |
75 | done; | |
76 | ||
77 | #===Determine function return code=== | |
78 | if [ "$returnState" = "true" ]; then | |
79 | return 0; | |
80 | else | |
81 | return 1; | |
82 | fi; | |
83 | } # Check that file exists | |
84 | checkdir() { | |
85 | # Desc: If arg is a dir path, save result in assoc array 'dirRollCall' | |
86 | # Usage: checkdir arg1 arg2 arg3 ... | |
87 | # Version 0.1.2 | |
88 | # Input: global assoc. array 'dirRollCall' | |
89 | # Output: adds/updates key(value) to global assoc array 'dirRollCall'; | |
90 | # Output: returns 0 if all args are dirs; 1 otherwise | |
91 | # Depends: Bash 5.0.3 | |
92 | local returnState | |
93 | ||
94 | #===Process Args=== | |
95 | for arg in "$@"; do | |
96 | if [ -z "$arg" ]; then | |
97 | dirRollCall["(Unspecified Dirname(s))"]="false"; returnState="false"; | |
98 | elif [ -d "$arg" ]; then | |
99 | dirRollCall["$arg"]="true"; | |
100 | if ! [ "$returnState" = "false" ]; then returnState="true"; fi | |
101 | else | |
102 | dirRollCall["$arg"]="false"; returnState="false"; | |
103 | fi | |
104 | done | |
105 | ||
106 | #===Determine function return code=== | |
107 | if [ "$returnState" = "true" ]; then | |
108 | return 0; | |
109 | else | |
110 | return 1; | |
111 | fi | |
112 | } # Check that dir exists | |
113 | displayMissing() { | |
114 | # Desc: Displays missing apps, files, and dirs | |
115 | # Usage: displayMissing | |
116 | # Version 1.0.0 | |
117 | # Input: associative arrays: appRollCall, fileRollCall, dirRollCall | |
118 | # Output: stderr: messages indicating missing apps, file, or dirs | |
119 | # Output: returns exit code 0 if nothing missing; 1 otherwise | |
120 | # Depends: bash 5, checkAppFileDir() | |
121 | local missingApps value appMissing missingFiles fileMissing | |
122 | local missingDirs dirMissing | |
123 | ||
124 | #==BEGIN Display errors== | |
125 | #===BEGIN Display Missing Apps=== | |
126 | missingApps="Missing apps :"; | |
127 | #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done | |
128 | for key in "${!appRollCall[@]}"; do | |
129 | value="${appRollCall[$key]}"; | |
130 | if [ "$value" = "false" ]; then | |
131 | #echo "DEBUG:Missing apps: $key => $value"; | |
132 | missingApps="$missingApps""$key "; | |
133 | appMissing="true"; | |
134 | fi; | |
135 | done; | |
136 | if [ "$appMissing" = "true" ]; then # Only indicate if an app is missing. | |
137 | echo "$missingApps" 1>&2; | |
138 | fi; | |
139 | unset value; | |
140 | #===END Display Missing Apps=== | |
141 | ||
142 | #===BEGIN Display Missing Files=== | |
143 | missingFiles="Missing files:"; | |
144 | #for key in "${!fileRollCall[@]}"; do echo "DEBUG:$key => ${fileRollCall[$key]}"; done | |
145 | for key in "${!fileRollCall[@]}"; do | |
146 | value="${fileRollCall[$key]}"; | |
147 | if [ "$value" = "false" ]; then | |
148 | #echo "DEBUG:Missing files: $key => $value"; | |
149 | missingFiles="$missingFiles""$key "; | |
150 | fileMissing="true"; | |
151 | fi; | |
152 | done; | |
153 | if [ "$fileMissing" = "true" ]; then # Only indicate if an app is missing. | |
154 | echo "$missingFiles" 1>&2; | |
155 | fi; | |
156 | unset value; | |
157 | #===END Display Missing Files=== | |
158 | ||
159 | #===BEGIN Display Missing Directories=== | |
160 | missingDirs="Missing dirs:"; | |
161 | #for key in "${!dirRollCall[@]}"; do echo "DEBUG:$key => ${dirRollCall[$key]}"; done | |
162 | for key in "${!dirRollCall[@]}"; do | |
163 | value="${dirRollCall[$key]}"; | |
164 | if [ "$value" = "false" ]; then | |
165 | #echo "DEBUG:Missing dirs: $key => $value"; | |
166 | missingDirs="$missingDirs""$key "; | |
167 | dirMissing="true"; | |
168 | fi; | |
169 | done; | |
170 | if [ "$dirMissing" = "true" ]; then # Only indicate if an dir is missing. | |
171 | echo "$missingDirs" 1>&2; | |
172 | fi; | |
173 | unset value; | |
174 | #===END Display Missing Directories=== | |
175 | ||
176 | #==END Display errors== | |
177 | #==BEGIN Determine function return code=== | |
178 | if [ "$appMissing" == "true" ] || [ "$fileMissing" == "true" ] || [ "$dirMissing" == "true" ]; then | |
179 | return 1; | |
180 | else | |
181 | return 0; | |
182 | fi | |
183 | #==END Determine function return code=== | |
184 | } # Display missing apps, files, dirs | |
185 | showUsage() { | |
186 | # Desc: Display script usage information | |
187 | # Usage: showUsage | |
188 | # Version 0.0.2 | |
189 | # Input: none | |
190 | # Output: stdout | |
191 | # Depends: GNU-coreutils 8.30 (cat) | |
192 | cat <<'EOF' | |
193 | USAGE: | |
194 | timelapse_from_videos.sh [ options ] [FILE...] | |
195 | ||
196 | OPTIONS: | |
197 | -h, --help | |
198 | Display help information. | |
199 | --version | |
200 | Display script version. | |
201 | -v, --verbose | |
202 | Display debugging info. | |
203 | -o, --output-file | |
204 | Define output file path. | |
205 | -- | |
206 | Indicate end of options. | |
207 | ||
208 | EXAMPLE: | |
209 | timelapse_from_videos.sh -o output.mp4 in1.mp4 in2.mp4 in3.mp4 | |
210 | timelapse_from_videos.sh -o output.mp4 -- in1.mp4 in2.mp4 in3.mp4 | |
211 | EOF | |
212 | } # Display information on how to use this script. | |
213 | showVersion() { | |
214 | # Desc: Displays script version and license information. | |
215 | # Usage: showVersion | |
216 | # Version: 0.0.2 | |
217 | # Input: scriptVersion var containing version string | |
218 | # Output: stdout | |
219 | # Depends: vbm(), yell, GNU-coreutils 8.30 | |
220 | ||
221 | # Initialize function | |
222 | vbm "DEBUG:showVersion function called." | |
223 | ||
224 | cat <<'EOF' | |
225 | timelapse_from_videos.sh 0.0.1 | |
226 | Copyright (C) 2022 Steven Baltakatei Sandoval | |
227 | License GPLv3: GNU GPL version 3 | |
228 | This is free software; you are free to change and redistribute it. | |
229 | There is NO WARRANTY, to the extent permitted by law. | |
230 | ||
231 | GNU Coreutils 8.32 | |
232 | Copyright (C) 2020 Free Software Foundation, Inc. | |
233 | License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>. | |
234 | This is free software: you are free to change and redistribute it. | |
235 | There is NO WARRANTY, to the extent permitted by law. | |
236 | EOF | |
237 | ||
238 | # End function | |
239 | vbm "DEBUG:showVersion function ended." | |
240 | return 0; # Function finished. | |
241 | } # Display script version. | |
242 | processArgs() { | |
243 | # Desc: Processes arguments provided to script. | |
244 | # Usage: processArgs "$@" | |
245 | # Version: 1.0.0 | |
246 | # Input: "$@" (list of arguments provided to the function) | |
247 | # Output: Sets following variables used by other functions: | |
248 | # opVerbose Indicates verbose mode enable status. (ex: "true", "false") | |
249 | # pathFileOut1 Path to output file. | |
250 | # opFileOut1_overwrite Indicates whether file pathFileOut1 should be overwritten (ex: "true", "false"). | |
251 | # arrayPosArgs Array of remaining positional argments | |
252 | # Depends: | |
253 | # yell() Displays messages to stderr. | |
254 | # vbm() Displays messsages to stderr if opVerbose set to "true". | |
255 | # showUsage() Displays usage information about parent script. | |
256 | # showVersion() Displays version about parent script. | |
257 | # arrayPosArgs Global array for storing non-option positional arguments (i.e. arguments following the `--` option). | |
258 | # External dependencies: bash (5.1.16), echo | |
259 | # Ref./Attrib.: | |
260 | # [1]: Marco Aurelio (2014-05-08). "echo that outputs to stderr". https://stackoverflow.com/a/23550347 | |
261 | # [2]: "Handling positional parameters" (2018-05-12). https://wiki.bash-hackers.org/scripting/posparams | |
262 | ||
263 | # Initialize function | |
264 | vbm "DEBUG:processArgs function called." | |
265 | ||
266 | # Perform work | |
267 | while [ ! $# -eq 0 ]; do # While number of arguments ($#) is not (!) equal to (-eq) zero (0). | |
268 | #yell "DEBUG:Starting processArgs while loop." # Debug stderr message. See [1]. | |
269 | #yell "DEBUG:Provided arguments are:""$*" # Debug stderr message. See [1]. | |
270 | case "$1" in | |
271 | -h | --help) showUsage; exit 1;; # Display usage. | |
272 | --version) showVersion; exit 1;; # Show version | |
273 | -v | --verbose) opVerbose="true"; vbm "DEBUG:Verbose mode enabled.";; # Enable verbose mode. See [1]. | |
274 | -o | --output-file) # Define output file path | |
275 | if [ -f "$2" ]; then # If $2 is file that exists, prompt user to continue to overwrite, set pathFileOut1 to $2, pop $2. | |
276 | yell "Specified output file $2 already exists. Overwrite? (y/n):" | |
277 | read -r m; case $m in | |
278 | y | Y | yes) opFileOut1_overwrite="true";; | |
279 | n | N | no) opFileOut1_overwrite="false";; | |
280 | *) yell "Invalid selection. Exiting."; exit 1;; | |
281 | esac | |
282 | if [ "$opFileOut1_overwrite" == "true" ]; then | |
283 | pathFileOut1="$2"; | |
284 | shift; | |
285 | vbm "DEBUG:Output file pathFileOut1 set to:""$2"; | |
286 | else | |
287 | yell "ERORR:Exiting in order to not overwrite output file:""$pathFileOut1"; | |
288 | exit 1; | |
289 | fi | |
290 | else | |
291 | pathFileOut1="$2"; | |
292 | shift; | |
293 | vbm "DEBUG:Output file pathFileOut1 set to:""$2"; | |
294 | fi ;; | |
295 | --) # End of all options. See [2]. | |
296 | shift; | |
297 | for arg in "$@"; do | |
298 | vbm "DEBUG:adding to arrayPosArgs:$arg"; | |
299 | arrayPosArgs+=("$arg"); | |
300 | done; | |
301 | break;; | |
302 | -*) showUsage; yell "ERROR: Unrecognized option."; exit 1;; # Display usage | |
303 | *) # Handle all other arguments. See [1]. | |
304 | ## Store in arrayPosArgs | |
305 | for arg in "$@"; do | |
306 | vbm "DEBUG:adding to arrayPosArgs:$arg"; | |
307 | arrayPosArgs+=("$arg"); | |
308 | done; | |
309 | break;; | |
310 | esac | |
311 | shift | |
312 | done | |
313 | ||
314 | # End function | |
315 | vbm "DEBUG:processArgs function ended." | |
316 | return 0; # Function finished. | |
317 | } # Evaluate script options from positional arguments (ex: $1, $2, $3, etc.). | |
318 | main() { | |
319 | # Output: | |
320 | # arrayPosArgs Array of remaining positional argments | |
321 | # Depends: ffmpeg 4.4.2, ffprobe 4.4.2 | |
322 | ||
323 | # Check input | |
324 | ## Check required commands | |
325 | checkapp ffmpeg ffprobe; | |
326 | ||
327 | ## Process args | |
328 | processArgs "$@"; | |
329 | vbm "$(declare -p arrayPosArgs;)"; | |
330 | ### Check non-option positional arguments are files | |
331 | for arg in "${arrayPosArgs[@]}"; do | |
332 | if [[ ! -f "$arg" ]]; then | |
333 | die "FATAL:Not a file:$arg (at $(readlink -f "$arg") )"; fi; | |
334 | done; | |
335 | ||
336 | # Check that files are video files | |
337 | ||
338 | ||
339 | yell "Done."; | |
340 | exit 0; | |
341 | }; # main program | |
342 | ||
343 | main "$@"; | |
344 | ||
345 | # Author: Steven Baltakatei Sandoval | |
346 | # License: GPLv3+ |