#!/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+