feat(unitproc/isAudio):Add script to filter audio files from find results
authorSteven Baltakatei Sandoval <baltakatei@gmail.com>
Sat, 4 Mar 2023 00:12:49 +0000 (00:12 +0000)
committerSteven Baltakatei Sandoval <baltakatei@gmail.com>
Sat, 4 Mar 2023 00:12:49 +0000 (00:12 +0000)
unitproc/isAudio [new file with mode: 0755]

diff --git a/unitproc/isAudio b/unitproc/isAudio
new file mode 100755 (executable)
index 0000000..943b0d1
--- /dev/null
@@ -0,0 +1,195 @@
+#!/usr/bin/env bash
+# Desc: Removes lines that aren't audio file paths
+# Usage: find . -type f | isAudio
+# Input: stdin
+# Output: stdout
+# Version: 0.0.1
+# Example: find ~/Music/ -type f | isAudio | mpv --playlist=-
+# Depends:
+#   BK-2020-03: read_stdin() 0.0.1
+
+declare -a music_codecs # Array for storing valid codec names (e.g. "aac" "mp3")
+
+# Adjustable parameters
+music_codecs=("vorbis" "aac" "mp3" "flac" "opus"); # whitelist of valid codec_names ffprobe might return
+
+yell() { echo "$0: $*" >&2; } # print script path and all args to stderr
+die() { yell "$*"; exit 111; } # same as yell() but non-zero exit status
+must() { "$@" || die "cannot $*"; } # runs args as command, reports args if command fails
+read_stdin() {
+    # Desc: Consumes stdin; outputs as stdout lines
+    # Input: stdin (consumes)
+    # Output: stdout (newline delimited)
+    # Example: printf "foo\nbar\n" | read_stdin
+    # Depends: GNU bash (version 5.1.16)
+    # Version: 0.0.1
+    local input_stdin output;
+
+    # Store stdin
+    if [[ -p /dev/stdin ]]; then
+        input_stdin="$(cat -)";
+    fi; 
+    
+    # Store as output array elements
+    ## Read in stdin
+    if [[ -n $input_stdin ]]; then
+        while read -r line; do
+            output+=("$line");
+        done < <(printf "%s\n" "$input_stdin");
+    fi;
+
+    # Print to stdout
+    printf "%s\n" "${output[@]}";
+}; # read stdin to stdout lines
+check_parsable_audio_ffprobe() {
+    # Desc: Checks if ffprobe returns valid audio codec name for file
+    # Usage: check_parsable_audio_ffprobe [path FILE]
+    # Version: 0.0.1
+    # Input: arg1: file path
+    # Output: exit code 0 if returns valid codec name; 1 otherwise
+    # Depends: ffprobe, die()
+    local file_in ffprobe_out
+
+    if [[ $# -ne 1 ]]; then die "ERROR:Invalid number of args:$#"; fi;
+    
+    file_in="$1";
+    
+    # Check if ffprobe detects an audio stream
+    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="true";
+    else
+       return_state="false";
+    fi;
+
+    # Fail if ffprobe returns no result
+    ffprobe_out="$(ffprobe -v error -select_streams a -show_entries stream=codec_name -of default=nokey=1:noprint_wrappers=1 "$file_in")";
+    if [[ -z $ffprobe_out ]]; then
+       return_state="false";
+    fi;
+    
+    # Report exit code
+    if [[ $return_state = "true" ]]; then
+       return 0;
+    else
+       return 1;
+    fi;
+} # Checks if file has valid codec name using ffprobe
+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
+checkIsInArray() {
+    # Desc: Checks if input arg is element in array
+    # Usage: checkIsInArray arg1 arg2
+    # Version: 0.0.1
+    # Input: arg1: test string
+    #        arg2: array
+    # Output: exit code 0 if test string is in array; 1 otherwise
+    # Example: checkIsInArray "foo" "${myArray[@]}"
+    # Ref/Attrib: [1] How do I check if variable is an array? https://stackoverflow.com/a/27254437
+    #             [2] How to pass an array as function argument? https://askubuntu.com/a/674347
+    local return_state input arg1 string_test
+    declare -a arg2 array_test
+    input=("$@") # See [2]
+    arg1="${input[0]}";
+    arg2=("${input[@]:1}");
+    #yell "DEBUG:input:${input[@]}";
+    #yell "DEBUG:arg1:${arg1[@]}";
+    #yell "DEBUG:arg2:${arg2[@]}";
+
+    string_test="$arg1";
+    array_test=("${arg2[@]}");
+
+    #yell "DEBUG:string_test:$string_test";
+    #yell "DEBUG:$(declare -p array_test)";
+    for element in "${array_test[@]}"; do
+       #yell "DEBUG:element:$element";
+       if [[ "$element" =~ ^"$string_test" ]]; then
+           return_state="true";
+           continue;
+       fi;
+    done;
+    
+    # Report exit code
+    if [[ $return_state == "true" ]]; then
+       return 0;
+    else
+       return 1;
+    fi;
+} # Check if string is element in array
+check_depends() {
+    if ! command -v ffprobe 1>/dev/random 2>&1; then
+        die "FATAL:Missing ffprobe."; fi;
+}; 
+main() {
+    # Input: array: music_codecs ("mp3" "aac" ...)
+    #        stdin
+    # Output: stdout
+    # Depends:
+    #   BK-2020-03: read_stdin() 0.0.1
+    
+    # check dependencies
+    check_depends;
+
+    # iterate through stdin lines
+    while read -r line; do
+        # check if file
+        if [[ ! -f $line ]]; then continue; fi; # reject
+
+        # check if valid codec
+        if ! check_parsable_audio_ffprobe "$line"; then
+            continue; fi; # reject
+
+        # Check if desired codec
+       file_format="$(get_audio_format "$line")";
+       if ! checkIsInArray "$file_format" "${music_codecs[@]}"; then
+            continue; fi; # reject
+
+        # Output line to stdout
+        printf "%s\n" "$line";
+    done < <(read_stdin);
+}; # main program
+
+main "$@" 2>/dev/random;
+
+# Author: Steven Baltakatei Sandoval
+# License: GPLv3+