#!/usr/bin/env bash
-# Desc: Wrapper for mpv that accepts directory paths via posargs or stdin lines
+# Desc: Wrapper for mpv that accepts directory or file paths via posargs or stdin lines
# Usage: bkmpv2 [DIR]
-# Version: 0.0.1
+# Version: 0.2.1
# Depends: GNU Parallel, GNU Bash v5.1.16, mpv v0.34.1, bc v1.07.1
# Ref/Attrib: [1] Tange, Ole. GNU Parallel with Bash Array. 2019-03-24. https://unix.stackexchange.com/a/508365/411854
# Example: find $HOME/Music -type d | bkmpv2
# Example: bkmpv2 $HOME/Music/
+# Example: find $HOME -type f -name "*.mp3" | bkmpv2
# Note: Does not follow symlinks
# Find settings
-firegex=".+\(aac\|aif\|aiff\|flac\|m4a\|mp3\|mp4\|ogg\|opus\|wav\)$"; # update according to `find . -type f | grep -Eo "\.([[:alnum:]])+$" | sort -u`
+firegex=".+\(aac\|aif\|aiff\|flac\|m4a\|mp3\|mp4\|ogg\|opus\|wav\)$"; # POSIX regex for find. Update according to `find . -type f | grep -Eo "\.([[:alnum:]])+$" | sort -u`
+file_regex=".+(aac|aif|aiff|flac|m4a|mp3|mp4|ogg|opus|wav)$"; # extended regex for Bash.
fsize="10k"; # default: minimum "10k"
fdepth_posarg="10"; # find depth for positional arguments
fdepth_stdin="1"; # find depth for stdin
# Input: global assoc. array 'appRollCall'
# Output: adds/updates key(value) to global assoc array 'appRollCall'
# Depends: bash 5.0.3
- local returnState
+ local returnState
#===Process Args===
for arg in "$@"; do
- if command -v "$arg" 1>/dev/null 2>&1; then # Check if arg is a valid command
- appRollCall[$arg]="true";
- if ! [ "$returnState" = "false" ]; then returnState="true"; fi;
- else
- appRollCall[$arg]="false"; returnState="false";
- fi;
+ if command -v "$arg" 1>/dev/null 2>&1; then # Check if arg is a valid command
+ appRollCall[$arg]="true";
+ if ! [ "$returnState" = "false" ]; then returnState="true"; fi;
+ else
+ appRollCall[$arg]="false"; returnState="false";
+ fi;
done;
#===Determine function return code===
if [ "$returnState" = "true" ]; then
- return 0;
+ return 0;
else
- return 1;
+ return 1;
fi;
} # Check that app exists
checkfile() {
#===Process Args===
for arg in "$@"; do
- if [ -f "$arg" ]; then
- fileRollCall["$arg"]="true";
- if ! [ "$returnState" = "false" ]; then returnState="true"; fi;
- else
- fileRollCall["$arg"]="false"; returnState="false";
- fi;
+ if [ -f "$arg" ]; then
+ fileRollCall["$arg"]="true";
+ if ! [ "$returnState" = "false" ]; then returnState="true"; fi;
+ else
+ fileRollCall["$arg"]="false"; returnState="false";
+ fi;
done;
-
+
#===Determine function return code===
if [ "$returnState" = "true" ]; then
- return 0;
+ return 0;
else
- return 1;
+ return 1;
fi;
} # Check that file exists
checkdir() {
#===Process Args===
for arg in "$@"; do
- if [ -d "$arg" ]; then
- dirRollCall["$arg"]="true";
- if ! [ "$returnState" = "false" ]; then returnState="true"; fi
- else
- dirRollCall["$arg"]="false"; returnState="false";
- fi
+ if [ -d "$arg" ]; then
+ dirRollCall["$arg"]="true";
+ if ! [ "$returnState" = "false" ]; then returnState="true"; fi
+ else
+ dirRollCall["$arg"]="false"; returnState="false";
+ fi
done
-
+
#===Determine function return code===
if [ "$returnState" = "true" ]; then
- return 0;
+ return 0;
else
- return 1;
+ return 1;
fi
} # Check that dir exists
displayMissing() {
# Depends: bash 5, checkAppFileDir()
local missingApps value appMissing missingFiles fileMissing
local missingDirs dirMissing
-
+
#==BEGIN Display errors==
#===BEGIN Display Missing Apps===
missingApps="Missing apps :";
#for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done
for key in "${!appRollCall[@]}"; do
- value="${appRollCall[$key]}";
- if [ "$value" = "false" ]; then
- #echo "DEBUG:Missing apps: $key => $value";
- missingApps="$missingApps""$key ";
- appMissing="true";
- fi;
+ value="${appRollCall[$key]}";
+ if [ "$value" = "false" ]; then
+ #echo "DEBUG:Missing apps: $key => $value";
+ missingApps="$missingApps""$key ";
+ appMissing="true";
+ fi;
done;
if [ "$appMissing" = "true" ]; then # Only indicate if an app is missing.
- echo "$missingApps" 1>&2;
+ echo "$missingApps" 1>&2;
fi;
unset value;
#===END Display Missing Apps===
missingFiles="Missing files:";
#for key in "${!fileRollCall[@]}"; do echo "DEBUG:$key => ${fileRollCall[$key]}"; done
for key in "${!fileRollCall[@]}"; do
- value="${fileRollCall[$key]}";
- if [ "$value" = "false" ]; then
- #echo "DEBUG:Missing files: $key => $value";
- missingFiles="$missingFiles""$key ";
- fileMissing="true";
- fi;
+ value="${fileRollCall[$key]}";
+ if [ "$value" = "false" ]; then
+ #echo "DEBUG:Missing files: $key => $value";
+ missingFiles="$missingFiles""$key ";
+ fileMissing="true";
+ fi;
done;
if [ "$fileMissing" = "true" ]; then # Only indicate if an app is missing.
- echo "$missingFiles" 1>&2;
+ echo "$missingFiles" 1>&2;
fi;
unset value;
#===END Display Missing Files===
missingDirs="Missing dirs:";
#for key in "${!dirRollCall[@]}"; do echo "DEBUG:$key => ${dirRollCall[$key]}"; done
for key in "${!dirRollCall[@]}"; do
- value="${dirRollCall[$key]}";
- if [ "$value" = "false" ]; then
- #echo "DEBUG:Missing dirs: $key => $value";
- missingDirs="$missingDirs""$key ";
- dirMissing="true";
- fi;
+ value="${dirRollCall[$key]}";
+ if [ "$value" = "false" ]; then
+ #echo "DEBUG:Missing dirs: $key => $value";
+ missingDirs="$missingDirs""$key ";
+ dirMissing="true";
+ fi;
done;
if [ "$dirMissing" = "true" ]; then # Only indicate if an dir is missing.
- echo "$missingDirs" 1>&2;
+ echo "$missingDirs" 1>&2;
fi;
unset value;
#===END Display Missing Directories===
#===Process Arg===
if [[ $# -ne 1 ]]; then
- die "ERROR:Invalid number of arguments:$#";
+ die "ERROR:Invalid number of arguments:$#";
fi;
-
+
RETEST1='^[0-9]+$'; # Regular Expression to test
if [[ ! $1 =~ $RETEST1 ]] ; then
- returnState="false";
+ returnState="false";
else
- returnState="true";
+ returnState="true";
fi;
#===Determine function return code===
if [ "$returnState" = "true" ]; then
- return 0;
+ return 0;
else
- return 1;
+ return 1;
fi;
} # Checks if arg is integer
read_stdin() {
# Store stdin
if [[ -p /dev/stdin ]]; then
input_stdin="$(cat -)";
- fi;
-
+ fi;
+
# Store as output array elements
## Read in stdin
if [[ -n $input_stdin ]]; then
# Depends: GNU bash (version 5.1.16)
# Version: 0.0.1
local input_psarg output;
-
+
# Store arguments
if [[ $# -gt 0 ]]; then
input_psarg="$*";
fi;
-
+
# Store as output array elements
## Read in positional arguments
if [[ -n $input_psarg ]]; then
if [[ ! -d "$1" ]]; then return 1; fi;
must find "$1" -maxdepth "$fdepth" -type f -iregex "$firegex" -size +"$fsize";
}; # print file list to stdout from dir with script parameters
+check_files() {
+ # Desc: Applies $file_regex to files specified by path
+ # Input: var: file_regex
+ # array: files_stdin
+ # Output: array: files_stdin
+ local file;
+ declare -a filtered_files_stdin;
+
+ for file in "${files_stdin[@]}"; do
+ if [[ "$file" =~ $file_regex ]]; then
+ filtered_files_stdin+=("$file");
+ fi;
+ done;
+ files_stdin=("${filtered_files_stdin[@]}");
+}; # apply $firegex to files_stdin array
main() {
# Input: var: firegex find iregex file name pattern
# var: fsize find minimum file siz
# var: fc_redbase logarithm base to reduce output file count
local re_dotfile;
- declare -a dirs_stdin dirs_psarg;
+ declare -a files_stdin dirs_stdin dirs_psarg;
declare -a paths_files;
declare list_paths_files;
declare -a cmd_args;
re_dotfile="^\."; # first char is a dot
while read -r line; do
line="$(readlink -e "$line")";
- # Check if dir
- if [[ ! -d "$line" ]]; then
- echo "ERROR:Not a dir:$line" 1>&2;
+ line_bn="$(basename "$line")";
+ # Check if dir and not dotfile
+ if [[ -d "$line" ]] && [[ ! "$line_bn" =~ $re_dotfile ]]; then
+ dirs_stdin+=("$line");
continue;
fi;
- dir_name="$(basename "$line")";
- # Exclude dotdirs
- if [[ "$dir_name" =~ $re_dotfile ]]; then
- echo "ERROR:Is a dotdir:$line" 1>&2;
- continue
- fi;
- dirs_stdin+=("$line");
- done < <(read_stdin);
- yell "STATUS:$SECONDS:Read stdin.";
- ## Read positional arguments as lines
- re_dotfile="^\."; # first char is a dot
- while read -r line; do
- line="$(readlink -e "$line")";
- # Check if dir
- if [[ ! -d "$line" ]]; then
- echo "ERROR:Not a dir:$line" 1>&2;
+
+ # Check if file
+ if [[ -f "$line" ]]; then
+ files_stdin+=("$line");
continue;
fi;
- dir_name="$(basename "$line")";
- # Exclude dotdirs
- if [[ "$dir_name" =~ $re_dotfile ]]; then
- echo "ERROR:Is a dotdir:$line" 1>&2;
- continue
- fi;
- dirs_psarg+=("$line");
- done < <(read_psarg "$@");
- yell "STATUS:$SECONDS:Read posargs.";
-
+
+ # Throw warning
+ yell "WARNING:Not a valid dir or file:$line";
+ done < <( read_stdin; read_psarg "$@"; );
+ yell "STATUS:$SECONDS:Read stdin and psargs.";
+
+ # Apply the $file_regex to $files_stdin array
+ check_files;
+
# Catch all arrays empty
- if [[ "${#dirs_stdin[@]}" -le 0 ]] && [[ "${#dirs_psarg[@]}" -le 0 ]]; then
- die "FATAL:No valid directories provided.";
+ if [[ "${#dirs_stdin[@]}" -le 0 ]] && \
+ [[ "${#dirs_psarg[@]}" -le 0 ]] && \
+ [[ "${#files_stdin[@]}" -le 0 ]]; then
+ die "FATAL:No valid directories or files provided.";
fi;
# Generate file list
+ ## Add stdin argument input
+ if [[ "${#files_stdin[@]}" -gt 0 ]]; then
+ paths_files+=("${files_stdin[@]}");
+ fi;
+
## Call find_filelist() in parallel for positional argument input
if [[ "${#dirs_psarg[@]}" -gt 0 ]]; then
fdepth="$fdepth_posarg"; export fdepth; # for dirs from positional arguments
bc_exp="$fc_falloff * (1 + l( $fc / $fc_falloff )/l($fc_redbase))";
fc_out="$( echo "$bc_exp" | bc -l )";
else
- fc_out="$fc";
+ fc_out="$fc";
fi;
## Reduce output file count by fixed fraction (bkshuf optimization)
fc_out="$(echo "$fc_out * 0.75" | bc -l)";
### Round file count down
fc_out="$(printf "%.0f" "$fc_out")"; # round float down to int
### Get neighbor-preserving shuffled subset (size $fc_out)
- yell "STATUS:$SECONDS:Selecting $fc_out files via bkshuf...";
+ yell "STATUS:$SECONDS:Selecting $fc_out of $fc files via bkshuf...";
must \
echo -n "$list_paths_files" | \
bkshuf "$fc_out" > "$list_paths_files_tmp";
cmd_args+=("--audio-display=no"); # disable video for audio files
cmd_args+=("--vid=no"); # donʼt show video track
cmd_args+=("--image-display-duration=0"); # don't show album art
- cmd_args+=("--af=scaletempo=stride=15:overlap=1:search=15"); # improve scrubbing
cmd_args+=("--playlist=$list_paths_files_tmp"); # read playlist
declare -p cmd_args; # debug
## Execute command