| 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+ |