2 # Desc: Extracts audio from video files
3 # Usage: bk_export_audio.sh [input_dir] ([output_dir])
6 declare -Ag appRollCall
# Associative array for storing app status
7 declare -Ag fileRollCall
# Associative array for storing file status
8 declare -Ag dirRollCall
# Associative array for storing dir status
10 yell
() { echo "$0: $*" >&2; } # print script path and all args to stderr
11 die
() { yell
"$*"; exit 111; } # same as yell() but non-zero exit status
12 try
() { "$@" || die
"cannot $*"; } # runs args as command, reports args if command fails
14 # Desc: If arg is a command, save result in assoc array 'appRollCall'
15 # Usage: checkapp arg1 arg2 arg3 ...
17 # Input: global assoc. array 'appRollCall'
18 # Output: adds/updates key(value) to global assoc array 'appRollCall'
24 if command -v "$arg" 1>/dev
/null
2>&1; then # Check if arg is a valid command
25 appRollCall
[$arg]="true";
26 if ! [ "$returnState" = "false" ]; then returnState
="true"; fi;
28 appRollCall
[$arg]="false"; returnState
="false";
32 #===Determine function return code===
33 if [ "$returnState" = "true" ]; then
38 } # Check that app exists
40 # Desc: If arg is a file path, save result in assoc array 'fileRollCall'
41 # Usage: checkfile arg1 arg2 arg3 ...
43 # Input: global assoc. array 'fileRollCall'
44 # Output: adds/updates key(value) to global assoc array 'fileRollCall';
45 # Output: returns 0 if app found, 1 otherwise
51 if [ -f "$arg" ]; then
52 fileRollCall
["$arg"]="true";
53 if ! [ "$returnState" = "false" ]; then returnState
="true"; fi;
55 fileRollCall
["$arg"]="false"; returnState
="false";
59 #===Determine function return code===
60 if [ "$returnState" = "true" ]; then
65 } # Check that file exists
67 # Desc: If arg is a dir path, save result in assoc array 'dirRollCall'
68 # Usage: checkdir arg1 arg2 arg3 ...
70 # Input: global assoc. array 'dirRollCall'
71 # Output: adds/updates key(value) to global assoc array 'dirRollCall';
72 # Output: returns 0 if all args are dirs; 1 otherwise
78 if [ -z "$arg" ]; then
79 dirRollCall
["(Unspecified Dirname(s))"]="false"; returnState
="false";
80 elif [ -d "$arg" ]; then
81 dirRollCall
["$arg"]="true";
82 if ! [ "$returnState" = "false" ]; then returnState
="true"; fi
84 dirRollCall
["$arg"]="false"; returnState
="false";
88 #===Determine function return code===
89 if [ "$returnState" = "true" ]; then
94 } # Check that dir exists
96 # Desc: Displays missing apps, files, and dirs
97 # Usage: displayMissing
99 # Input: associative arrays: appRollCall, fileRollCall, dirRollCall
100 # Output: stderr: messages indicating missing apps, file, or dirs
101 # Depends: bash 5, checkAppFileDir()
102 local missingApps value appMissing missingFiles fileMissing
103 local missingDirs dirMissing
105 #==BEGIN Display errors==
106 #===BEGIN Display Missing Apps===
107 missingApps
="Missing apps :";
108 #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done
109 for key
in "${!appRollCall[@]}"; do
110 value
="${appRollCall[$key]}";
111 if [ "$value" = "false" ]; then
112 #echo "DEBUG:Missing apps: $key => $value";
113 missingApps
="$missingApps""$key ";
117 if [ "$appMissing" = "true" ]; then # Only indicate if an app is missing.
118 echo "$missingApps" 1>&2;
121 #===END Display Missing Apps===
123 #===BEGIN Display Missing Files===
124 missingFiles
="Missing files:";
125 #for key in "${!fileRollCall[@]}"; do echo "DEBUG:$key => ${fileRollCall[$key]}"; done
126 for key
in "${!fileRollCall[@]}"; do
127 value
="${fileRollCall[$key]}";
128 if [ "$value" = "false" ]; then
129 #echo "DEBUG:Missing files: $key => $value";
130 missingFiles
="$missingFiles""$key ";
134 if [ "$fileMissing" = "true" ]; then # Only indicate if an app is missing.
135 echo "$missingFiles" 1>&2;
138 #===END Display Missing Files===
140 #===BEGIN Display Missing Directories===
141 missingDirs
="Missing dirs:";
142 #for key in "${!dirRollCall[@]}"; do echo "DEBUG:$key => ${dirRollCall[$key]}"; done
143 for key
in "${!dirRollCall[@]}"; do
144 value
="${dirRollCall[$key]}";
145 if [ "$value" = "false" ]; then
146 #echo "DEBUG:Missing dirs: $key => $value";
147 missingDirs
="$missingDirs""$key ";
151 if [ "$dirMissing" = "true" ]; then # Only indicate if an dir is missing.
152 echo "$missingDirs" 1>&2;
155 #===END Display Missing Directories===
157 #==END Display errors==
158 } # Display missing apps, files, dirs
160 # Desc: Display script usage information
165 # Depends: GNU-coreutils 8.30 (cat)
168 bk_export_audio.sh [DIR in] ([DIR out])
171 bk_export_audio.sh ./videos/ ./exported_audio/
172 bk_export_audio.sh ./videos/
174 } # Display information on how to use this script.
176 # Desc: Gets audio format of file as string
177 # Usage: get_audio_format arg1
180 # Input: arg1: input file path
181 # Output: stdout (if valid audio format)
182 # exit code 0 if audio file; 1 otherwise
183 # Example: get_audio_format myvideo.mp4
184 # Note: Would return "opus" if full ffprobe report had 'Audio: opus, 48000 Hz, stereo, fltp'
185 # Note: Not tested with videos containing multiple video streams
186 # Ref/Attrib: [1] https://stackoverflow.com/questions/5618363/is-there-a-way-to-use-ffmpeg-to-determine-the-encoding-of-a-file-before-transcod
187 # [2] https://stackoverflow.com/questions/44123532/how-to-find-out-the-file-extension-for-extracting-audio-tracks-with-ffmpeg-and-p#comment88464070_50723126
188 local audio_format file_in
;
192 # Return error exit code if not audio file
193 ## Return error if ffprobe itself exited on error
194 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
195 return_state
="false";
199 audio_format
="$(ffprobe -v error -select_streams a -show_entries stream=codec_name -of default=nokey=1:noprint_wrappers=1 "$file_in")"; # see [1]
201 ## Return error if audio format is incorrectly formatted (e.g. reject if contains spaces)
202 pattern
="^[[:alnum:]]+$"; # alphanumeric string with no spaces
203 if [[ $audio_format =~
$pattern ]]; then
205 # Report audio format
206 echo "$audio_format";
208 return_state
="false";
212 if [[ $return_state = "true" ]]; then
217 } # Get audio format as stdout
218 extract_audio_file
() {
219 # Desc: Use ffmpeg to creates audio file from input video file
220 # Usage: extract_audio_file arg1 arg2 arg3
222 # Input: arg1: input video file path
223 # arg2: desired output file extension
224 # arg3: output dir path
225 # Output: audio file at path [arg3]/[arg1].[arg2]
226 local file_in file_in_ext dir_out file_in_basename
;
232 file_in_basename
="$(basename "$file_in")";
233 ffmpeg
-i "$file_in" -vn -acodec copy
"$dir_out"/"$file_in_basename".
"$file_in_ext";
241 # Check argument count
242 if [[ $# -lt 1 ]]; then
244 die
"ERROR:Not enough arguments:$#";
248 checkapp ffmpeg ffprobe
date;
251 if ! checkdir
"$dir_in"; then
254 die
"ERROR:Missing input directory."
256 if ! checkdir
"$dir_out"; then
257 yell
"NOTICE:Output directory not specified. Creating output directory in current working directory:$script_pwd";
258 timestamp
="$(date +%Y%m%dT%H%M%S%z)"; # iso-8601 without separators
259 dir_out
="$script_pwd"/"$timestamp"..output
;
260 try mkdir
"$dir_out";
264 yell
"DEBUG:dir_in:$dir_in":
265 yell
"DEBUG:dir_out:$dir_out";
266 for file in "$dir_in"/*; do
267 aud_format
="$(get_audio_format "$file")"; # Get audio format as file extension string
268 file_basename
="$(basename "$file")"; # Get basename for debugging
269 yell
"DEBUG:file_basename:$file_basename";
270 yell
"DEBUG:aud_format:$aud_format";
273 # Ignore files that return blank $aud_format
274 if [[ -z $aud_format ]]; then
275 yell
"DEBUG:Not an audio file:$file";
279 extract_audio_file
"$file" "$aud_format" "$dir_out";
285 # Author: Steven Baltaktei Sandoval