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