feat(u/bk_export_audio.sh):Bash script to get audio files from video
authorSteven Baltakatei Sandoval <baltakatei@gmail.com>
Sat, 8 Jan 2022 03:06:37 +0000 (03:06 +0000)
committerSteven Baltakatei Sandoval <baltakatei@gmail.com>
Sat, 8 Jan 2022 03:06:37 +0000 (03:06 +0000)
unitproc/bk_export_audio.sh [new file with mode: 0755]

diff --git a/unitproc/bk_export_audio.sh b/unitproc/bk_export_audio.sh
new file mode 100755 (executable)
index 0000000..5c615e8
--- /dev/null
@@ -0,0 +1,286 @@
+#!/bin/bash
+# Desc: Extracts audio from video files
+# Usage: bk_export_audio.sh [input_dir] ([output_dir])
+# Version: 0.0.1
+
+declare -Ag appRollCall # Associative array for storing app status
+declare -Ag fileRollCall # Associative array for storing file status
+declare -Ag dirRollCall # Associative array for storing dir status
+
+yell() { echo "$0: $*" >&2; } # print script path and all args to stderr
+die() { yell "$*"; exit 111; } # same as yell() but non-zero exit status
+try() { "$@" || die "cannot $*"; } # runs args as command, reports args if command fails
+checkapp() {
+    # Desc: If arg is a command, save result in assoc array 'appRollCall'
+    # Usage: checkapp arg1 arg2 arg3 ...
+    # Version: 0.1.1
+    # Input: global assoc. array 'appRollCall'
+    # Output: adds/updates key(value) to global assoc array 'appRollCall'
+    # Depends: bash 5.0.3
+    local returnState    
+
+    #===Process Args===
+    for arg in "$@"; do
+       if command -v "$arg" 1>/dev/null 2>&1; then # Check if arg is a valid command
+           appRollCall[$arg]="true";
+           if ! [ "$returnState" = "false" ]; then returnState="true"; fi;
+       else
+           appRollCall[$arg]="false"; returnState="false";
+       fi;
+    done;
+
+    #===Determine function return code===
+    if [ "$returnState" = "true" ]; then
+       return 0;
+    else
+       return 1;
+    fi;
+} # Check that app exists
+checkfile() {
+    # Desc: If arg is a file path, save result in assoc array 'fileRollCall'
+    # Usage: checkfile arg1 arg2 arg3 ...
+    # Version: 0.1.1
+    # Input: global assoc. array 'fileRollCall'
+    # Output: adds/updates key(value) to global assoc array 'fileRollCall';
+    # Output: returns 0 if app found, 1 otherwise
+    # Depends: bash 5.0.3
+    local returnState
+
+    #===Process Args===
+    for arg in "$@"; do
+       if [ -f "$arg" ]; then
+           fileRollCall["$arg"]="true";
+           if ! [ "$returnState" = "false" ]; then returnState="true"; fi;
+       else
+           fileRollCall["$arg"]="false"; returnState="false";
+       fi;
+    done;
+    
+    #===Determine function return code===
+    if [ "$returnState" = "true" ]; then
+       return 0;
+    else
+       return 1;
+    fi;
+} # Check that file exists
+checkdir() {
+    # Desc: If arg is a dir path, save result in assoc array 'dirRollCall'
+    # Usage: checkdir arg1 arg2 arg3 ...
+    # Version 0.1.2
+    # Input: global assoc. array 'dirRollCall'
+    # Output: adds/updates key(value) to global assoc array 'dirRollCall';
+    # Output: returns 0 if all args are dirs; 1 otherwise
+    # Depends: Bash 5.0.3
+    local returnState
+
+    #===Process Args===
+    for arg in "$@"; do
+       if [ -z "$arg" ]; then
+           dirRollCall["(Unspecified Dirname(s))"]="false"; returnState="false";
+       elif [ -d "$arg" ]; then
+           dirRollCall["$arg"]="true";
+           if ! [ "$returnState" = "false" ]; then returnState="true"; fi
+       else
+           dirRollCall["$arg"]="false"; returnState="false";
+       fi
+    done
+    
+    #===Determine function return code===
+    if [ "$returnState" = "true" ]; then
+       return 0;
+    else
+       return 1;
+    fi
+} # Check that dir exists
+displayMissing() {
+    # Desc: Displays missing apps, files, and dirs
+    # Usage: displayMissing
+    # Version 0.1.1
+    # Input: associative arrays: appRollCall, fileRollCall, dirRollCall
+    # Output: stderr: messages indicating missing apps, file, or dirs
+    # Depends: bash 5, checkAppFileDir()
+    local missingApps value appMissing missingFiles fileMissing
+    local missingDirs dirMissing
+    
+    #==BEGIN Display errors==
+    #===BEGIN Display Missing Apps===
+    missingApps="Missing apps  :";
+    #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done
+    for key in "${!appRollCall[@]}"; do
+       value="${appRollCall[$key]}";
+       if [ "$value" = "false" ]; then
+           #echo "DEBUG:Missing apps: $key => $value";
+           missingApps="$missingApps""$key ";
+           appMissing="true";
+       fi;
+    done;
+    if [ "$appMissing" = "true" ]; then  # Only indicate if an app is missing.
+       echo "$missingApps" 1>&2;
+    fi;
+    unset value;
+    #===END Display Missing Apps===
+
+    #===BEGIN Display Missing Files===
+    missingFiles="Missing files:";
+    #for key in "${!fileRollCall[@]}"; do echo "DEBUG:$key => ${fileRollCall[$key]}"; done
+    for key in "${!fileRollCall[@]}"; do
+       value="${fileRollCall[$key]}";
+       if [ "$value" = "false" ]; then
+           #echo "DEBUG:Missing files: $key => $value";
+           missingFiles="$missingFiles""$key ";
+           fileMissing="true";
+       fi;
+    done;
+    if [ "$fileMissing" = "true" ]; then  # Only indicate if an app is missing.
+       echo "$missingFiles" 1>&2;
+    fi;
+    unset value;
+    #===END Display Missing Files===
+
+    #===BEGIN Display Missing Directories===
+    missingDirs="Missing dirs:";
+    #for key in "${!dirRollCall[@]}"; do echo "DEBUG:$key => ${dirRollCall[$key]}"; done
+    for key in "${!dirRollCall[@]}"; do
+       value="${dirRollCall[$key]}";
+       if [ "$value" = "false" ]; then
+           #echo "DEBUG:Missing dirs: $key => $value";
+           missingDirs="$missingDirs""$key ";
+           dirMissing="true";
+       fi;
+    done;
+    if [ "$dirMissing" = "true" ]; then  # Only indicate if an dir is missing.
+       echo "$missingDirs" 1>&2;
+    fi;
+    unset value;
+    #===END Display Missing Directories===
+
+    #==END Display errors==
+} # Display missing apps, files, dirs
+showUsage() {
+    # Desc: Display script usage information
+    # Usage: showUsage
+    # Version 0.0.1
+    # Input: none
+    # Output: stdout
+    # Depends: GNU-coreutils 8.30 (cat)
+    cat <<'EOF'
+    USAGE:
+        bk_export_audio.sh [DIR in] ([DIR out])
+
+    EXAMPLE:
+      bk_export_audio.sh ./videos/ ./exported_audio/
+      bk_export_audio.sh ./videos/
+EOF
+} # Display information on how to use this script.
+get_audio_format() {
+    # Desc: Gets audio format of file as string
+    # Usage: get_audio_format arg1
+    # Depends: ffprobe
+    # Version: 0.0.1
+    # Input: arg1: input file path
+    # Output: stdout (if valid audio format)
+    #         exit code 0 if audio file; 1 otherwise
+    # Example: get_audio_format myvideo.mp4
+    #   Note: Would return "opus" if full ffprobe report had 'Audio: opus, 48000 Hz, stereo, fltp'
+    # Note: Not tested with videos containing multiple video streams
+    # Ref/Attrib: [1] https://stackoverflow.com/questions/5618363/is-there-a-way-to-use-ffmpeg-to-determine-the-encoding-of-a-file-before-transcod
+    #             [2] https://stackoverflow.com/questions/44123532/how-to-find-out-the-file-extension-for-extracting-audio-tracks-with-ffmpeg-and-p#comment88464070_50723126
+    local audio_format file_in;
+    local return_state;
+    file_in="$1";
+
+    # Return error exit code if not audio file
+    ## Return error if ffprobe itself exited on error
+    if ! ffprobe -v error -select_streams a -show_entries stream=codec_name -of default=nokey=1:noprint_wrappers=1 "$file_in" 1>/dev/null 2>&1; then
+       return_state="false";
+    fi;
+    
+    # Get audio format
+    audio_format="$(ffprobe -v error -select_streams a -show_entries stream=codec_name -of default=nokey=1:noprint_wrappers=1 "$file_in")"; # see [1]
+
+    ## Return error if audio format is incorrectly formatted (e.g. reject if contains spaces)
+    pattern="^[[:alnum:]]+$"; # alphanumeric string with no spaces
+    if [[ $audio_format =~ $pattern ]]; then
+       return_state="true";
+       # Report audio format
+       echo "$audio_format";
+    else
+       return_state="false";
+    fi;
+    
+    # Report exit code
+    if [[ $return_state = "true" ]]; then
+       return 0;
+    else
+       return 1;
+    fi;
+} # Get audio format as stdout
+extract_audio_file() {
+    # Desc: Use ffmpeg to creates audio file from input video file
+    # Usage: extract_audio_file arg1 arg2 arg3
+    # Depends: ffmpeg
+    # Input: arg1: input video file path
+    #        arg2: desired output file extension
+    #        arg3: output dir path
+    # Output: audio file at path [arg3]/[arg1].[arg2]
+    local file_in file_in_ext dir_out file_in_basename;
+    file_in="$1";
+    file_in_ext="$2";
+    dir_out="$3";
+
+    # Extract audio file
+    file_in_basename="$(basename "$file_in")";    
+    ffmpeg -i "$file_in" -vn -acodec copy "$dir_out"/"$file_in_basename"."$file_in_ext";
+}
+
+main() {
+    script_pwd="$(pwd)";
+    dir_in="$1";
+    dir_out="$2";
+
+    # Check argument count
+    if [[ $# -lt 1 ]]; then
+       showUsage;
+       die "ERROR:Not enough arguments:$#";
+    fi;
+
+    # Check apps, dirs
+    checkapp ffmpeg ffprobe date;
+    displayMissing;
+
+    if ! checkdir "$dir_in"; then
+       showUsage;
+       displayMissing;
+       die "ERROR:Missing input directory."
+    fi;
+    if ! checkdir "$dir_out"; then
+       yell "NOTICE:Output directory not specified. Creating output directory in current working directory:$script_pwd";
+       timestamp="$(date +%Y%m%dT%H%M%S%z)"; # iso-8601 without separators
+       dir_out="$script_pwd"/"$timestamp"..output;
+       try mkdir "$dir_out";
+    fi;
+
+    # Do work
+    yell "DEBUG:dir_in:$dir_in":
+    yell "DEBUG:dir_out:$dir_out";
+    for file in "$dir_in"/*; do
+       aud_format="$(get_audio_format "$file")"; # Get audio format as file extension string
+       file_basename="$(basename "$file")"; # Get basename for debugging
+       yell "DEBUG:file_basename:$file_basename";
+       yell "DEBUG:aud_format:$aud_format";
+       yell "DEBUG:";
+
+       # Ignore files that return blank $aud_format
+       if [[ -z $aud_format ]]; then
+           yell "DEBUG:Not an audio file:$file";
+           continue;
+       fi;
+       
+       extract_audio_file "$file" "$aud_format" "$dir_out";
+    done;
+} # main program
+
+main "$@";
+
+# Author: Steven Baltaktei Sandoval
+# License: GPLv3+