feat(unitproc/isAudio):Add script to filter audio files from find results
[BK-2020-03.git] / unitproc / isAudio
1 #!/usr/bin/env bash
2 # Desc: Removes lines that aren't audio file paths
3 # Usage: find . -type f | isAudio
4 # Input: stdin
5 # Output: stdout
6 # Version: 0.0.1
7 # Example: find ~/Music/ -type f | isAudio | mpv --playlist=-
8 # Depends:
9 # BK-2020-03: read_stdin() 0.0.1
10
11 declare -a music_codecs # Array for storing valid codec names (e.g. "aac" "mp3")
12
13 # Adjustable parameters
14 music_codecs=("vorbis" "aac" "mp3" "flac" "opus"); # whitelist of valid codec_names ffprobe might return
15
16 yell() { echo "$0: $*" >&2; } # print script path and all args to stderr
17 die() { yell "$*"; exit 111; } # same as yell() but non-zero exit status
18 must() { "$@" || die "cannot $*"; } # runs args as command, reports args if command fails
19 read_stdin() {
20 # Desc: Consumes stdin; outputs as stdout lines
21 # Input: stdin (consumes)
22 # Output: stdout (newline delimited)
23 # Example: printf "foo\nbar\n" | read_stdin
24 # Depends: GNU bash (version 5.1.16)
25 # Version: 0.0.1
26 local input_stdin output;
27
28 # Store stdin
29 if [[ -p /dev/stdin ]]; then
30 input_stdin="$(cat -)";
31 fi;
32
33 # Store as output array elements
34 ## Read in stdin
35 if [[ -n $input_stdin ]]; then
36 while read -r line; do
37 output+=("$line");
38 done < <(printf "%s\n" "$input_stdin");
39 fi;
40
41 # Print to stdout
42 printf "%s\n" "${output[@]}";
43 }; # read stdin to stdout lines
44 check_parsable_audio_ffprobe() {
45 # Desc: Checks if ffprobe returns valid audio codec name for file
46 # Usage: check_parsable_audio_ffprobe [path FILE]
47 # Version: 0.0.1
48 # Input: arg1: file path
49 # Output: exit code 0 if returns valid codec name; 1 otherwise
50 # Depends: ffprobe, die()
51 local file_in ffprobe_out
52
53 if [[ $# -ne 1 ]]; then die "ERROR:Invalid number of args:$#"; fi;
54
55 file_in="$1";
56
57 # Check if ffprobe detects an audio stream
58 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
59 return_state="true";
60 else
61 return_state="false";
62 fi;
63
64 # Fail if ffprobe returns no result
65 ffprobe_out="$(ffprobe -v error -select_streams a -show_entries stream=codec_name -of default=nokey=1:noprint_wrappers=1 "$file_in")";
66 if [[ -z $ffprobe_out ]]; then
67 return_state="false";
68 fi;
69
70 # Report exit code
71 if [[ $return_state = "true" ]]; then
72 return 0;
73 else
74 return 1;
75 fi;
76 } # Checks if file has valid codec name using ffprobe
77 get_audio_format() {
78 # Desc: Gets audio format of file as string
79 # Usage: get_audio_format arg1
80 # Depends: ffprobe
81 # Version: 0.0.1
82 # Input: arg1: input file path
83 # Output: stdout (if valid audio format)
84 # exit code 0 if audio file; 1 otherwise
85 # Example: get_audio_format myvideo.mp4
86 # Note: Would return "opus" if full ffprobe report had 'Audio: opus, 48000 Hz, stereo, fltp'
87 # Note: Not tested with videos containing multiple video streams
88 # Ref/Attrib: [1] https://stackoverflow.com/questions/5618363/is-there-a-way-to-use-ffmpeg-to-determine-the-encoding-of-a-file-before-transcod
89 # [2] https://stackoverflow.com/questions/44123532/how-to-find-out-the-file-extension-for-extracting-audio-tracks-with-ffmpeg-and-p#comment88464070_50723126
90 local audio_format file_in;
91 local return_state;
92 file_in="$1";
93
94 # Return error exit code if not audio file
95 ## Return error if ffprobe itself exited on error
96 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
97 return_state="false";
98 fi;
99
100 # Get audio format
101 audio_format="$(ffprobe -v error -select_streams a -show_entries stream=codec_name -of default=nokey=1:noprint_wrappers=1 "$file_in")"; # see [1]
102
103 ## Return error if audio format is incorrectly formatted (e.g. reject if contains spaces)
104 pattern="^[[:alnum:]]+$"; # alphanumeric string with no spaces
105 if [[ $audio_format =~ $pattern ]]; then
106 return_state="true";
107 # Report audio format
108 echo "$audio_format";
109 else
110 return_state="false";
111 fi;
112
113 # Report exit code
114 if [[ $return_state = "true" ]]; then
115 return 0;
116 else
117 return 1;
118 fi;
119 } # Get audio format as stdout
120 checkIsInArray() {
121 # Desc: Checks if input arg is element in array
122 # Usage: checkIsInArray arg1 arg2
123 # Version: 0.0.1
124 # Input: arg1: test string
125 # arg2: array
126 # Output: exit code 0 if test string is in array; 1 otherwise
127 # Example: checkIsInArray "foo" "${myArray[@]}"
128 # Ref/Attrib: [1] How do I check if variable is an array? https://stackoverflow.com/a/27254437
129 # [2] How to pass an array as function argument? https://askubuntu.com/a/674347
130 local return_state input arg1 string_test
131 declare -a arg2 array_test
132 input=("$@") # See [2]
133 arg1="${input[0]}";
134 arg2=("${input[@]:1}");
135 #yell "DEBUG:input:${input[@]}";
136 #yell "DEBUG:arg1:${arg1[@]}";
137 #yell "DEBUG:arg2:${arg2[@]}";
138
139 string_test="$arg1";
140 array_test=("${arg2[@]}");
141
142 #yell "DEBUG:string_test:$string_test";
143 #yell "DEBUG:$(declare -p array_test)";
144 for element in "${array_test[@]}"; do
145 #yell "DEBUG:element:$element";
146 if [[ "$element" =~ ^"$string_test" ]]; then
147 return_state="true";
148 continue;
149 fi;
150 done;
151
152 # Report exit code
153 if [[ $return_state == "true" ]]; then
154 return 0;
155 else
156 return 1;
157 fi;
158 } # Check if string is element in array
159 check_depends() {
160 if ! command -v ffprobe 1>/dev/random 2>&1; then
161 die "FATAL:Missing ffprobe."; fi;
162 };
163 main() {
164 # Input: array: music_codecs ("mp3" "aac" ...)
165 # stdin
166 # Output: stdout
167 # Depends:
168 # BK-2020-03: read_stdin() 0.0.1
169
170 # check dependencies
171 check_depends;
172
173 # iterate through stdin lines
174 while read -r line; do
175 # check if file
176 if [[ ! -f $line ]]; then continue; fi; # reject
177
178 # check if valid codec
179 if ! check_parsable_audio_ffprobe "$line"; then
180 continue; fi; # reject
181
182 # Check if desired codec
183 file_format="$(get_audio_format "$line")";
184 if ! checkIsInArray "$file_format" "${music_codecs[@]}"; then
185 continue; fi; # reject
186
187 # Output line to stdout
188 printf "%s\n" "$line";
189 done < <(read_stdin);
190 }; # main program
191
192 main "$@" 2>/dev/random;
193
194 # Author: Steven Baltakatei Sandoval
195 # License: GPLv3+