From 8a4fc9ec681685229f902e2fe426fc7b3f851833 Mon Sep 17 00:00:00 2001 From: Steven Baltakatei Sandoval Date: Sat, 4 Mar 2023 00:12:49 +0000 Subject: [PATCH] feat(unitproc/isAudio):Add script to filter audio files from find results --- unitproc/isAudio | 195 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100755 unitproc/isAudio diff --git a/unitproc/isAudio b/unitproc/isAudio new file mode 100755 index 0000000..943b0d1 --- /dev/null +++ b/unitproc/isAudio @@ -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+ -- 2.30.2