update(README):Add link to reboil.com wiki entry for project
[BK-2020-03.git] / user / draft / timelapse_from_videos.sh
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+