2 # Desc: Combines video files into time lapse video. 
   3 # Usage: timelapse_from_videos.sh [FILES] 
   4 # Example: timelapse_from_videos.sh ./*.MP4 
   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 
  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 
  15     # Description: Prints verbose message ("vbm") to stderr if opVerbose is set to "true". 
  16     # Usage: vbm "DEBUG :verbose message here" 
  21     # Depends: bash 5.1.16, GNU-coreutils 8.30 (echo, date) 
  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. 
  29     return 0; # Function finished. 
  30 } # Displays message if opVerbose true 
  32     # Desc: If arg is a command, save result in assoc array 'appRollCall' 
  33     # Usage: checkapp arg1 arg2 arg3 ... 
  35     # Input: global assoc. array 'appRollCall' 
  36     # Output: adds/updates key(value) to global assoc array 'appRollCall' 
  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; 
  46             appRollCall
[$arg]="false"; returnState
="false"; 
  50     #===Determine function return code=== 
  51     if [ "$returnState" = "true" ]; then 
  56 } # Check that app exists 
  58     # Desc: If arg is a file path, save result in assoc array 'fileRollCall' 
  59     # Usage: checkfile arg1 arg2 arg3 ... 
  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 
  69         if [ -f "$arg" ]; then 
  70             fileRollCall
["$arg"]="true"; 
  71             if ! [ "$returnState" = "false" ]; then returnState
="true"; fi; 
  73             fileRollCall
["$arg"]="false"; returnState
="false"; 
  77     #===Determine function return code=== 
  78     if [ "$returnState" = "true" ]; then 
  83 } # Check that file exists 
  85     # Desc: If arg is a dir path, save result in assoc array 'dirRollCall' 
  86     # Usage: checkdir arg1 arg2 arg3 ... 
  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 
  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 
 102             dirRollCall
["$arg"]="false"; returnState
="false"; 
 106     #===Determine function return code=== 
 107     if [ "$returnState" = "true" ]; then 
 112 } # Check that dir exists 
 114     # Desc: Displays missing apps, files, and dirs 
 115     # Usage: displayMissing 
 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
 
 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 "; 
 136     if [ "$appMissing" = "true" ]; then  # Only indicate if an app is missing. 
 137         echo "$missingApps" 1>&2; 
 140     #===END Display Missing Apps=== 
 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 "; 
 153     if [ "$fileMissing" = "true" ]; then  # Only indicate if an app is missing. 
 154         echo "$missingFiles" 1>&2; 
 157     #===END Display Missing Files=== 
 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 "; 
 170     if [ "$dirMissing" = "true" ]; then  # Only indicate if an dir is missing. 
 171         echo "$missingDirs" 1>&2; 
 174     #===END Display Missing Directories=== 
 176     #==END Display errors== 
 177     #==BEGIN Determine function return code=== 
 178     if [ "$appMissing" == "true" ] || 
[ "$fileMissing" == "true" ] || 
[ "$dirMissing" == "true" ]; then 
 183     #==END Determine function return code=== 
 184 } # Display missing apps, files, dirs 
 186     # Desc: Display script usage information 
 191     # Depends: GNU-coreutils 8.30 (cat) 
 194         timelapse_from_videos.sh [ options ] [FILE...] 
 198                 Display help information. 
 200                 Display script version. 
 202                 Display debugging info. 
 204                 Define output file path. 
 206                 Indicate end of options. 
 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 
 212 } # Display information on how to use this script. 
 214     # Desc: Displays script version and license information. 
 217     # Input: scriptVersion   var containing version string 
 219     # Depends: vbm(), yell, GNU-coreutils 8.30 
 221     # Initialize function 
 222     vbm 
"DEBUG:showVersion function called." 
 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. 
 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. 
 239     vbm 
"DEBUG:showVersion function ended." 
 240     return 0; # Function finished. 
 241 } # Display script version. 
 243     # Desc: Processes arguments provided to script. 
 244     # Usage: processArgs "$@" 
 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 
 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 
 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 
 263     # Initialize function 
 264     vbm 
"DEBUG:processArgs function called." 
 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]. 
 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;; 
 282                     if [ "$opFileOut1_overwrite" == "true" ]; then 
 285                         vbm 
"DEBUG:Output file pathFileOut1 set to:""$2"; 
 287                         yell 
"ERORR:Exiting in order to not overwrite output file:""$pathFileOut1"; 
 293                     vbm 
"DEBUG:Output file pathFileOut1 set to:""$2"; 
 295             --) # End of all options. See [2]. 
 298                     vbm 
"DEBUG:adding to arrayPosArgs:$arg"; 
 299                     arrayPosArgs
+=("$arg"); 
 302             -*) showUsage
; yell 
"ERROR: Unrecognized option."; exit 1;; # Display usage 
 303             *) # Handle all other arguments. See [1]. 
 304                 ## Store in arrayPosArgs 
 306                     vbm 
"DEBUG:adding to arrayPosArgs:$arg"; 
 307                     arrayPosArgs
+=("$arg"); 
 315     vbm 
"DEBUG:processArgs function ended." 
 316     return  0; # Function finished. 
 317 } # Evaluate script options from positional arguments (ex: $1, $2, $3, etc.). 
 320     #   arrayPosArgs         Array of remaining positional argments 
 321     # Depends: ffmpeg 4.4.2, ffprobe 4.4.2 
 324     ## Check required commands 
 325     checkapp ffmpeg ffprobe
; 
 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; 
 336     # Check that files are video files 
 345 # Author: Steven Baltakatei Sandoval