feat(user/bkmpv2):Filter audio files by file extension
[BK-2020-03.git] / user / bkmpv2
index 19f605e2ebf90cfcf4d860b65c71d984542c01c3..e4128f04fa4b2d57cd906f376e36298f3dacea57 100755 (executable)
@@ -1,15 +1,17 @@
 #!/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.0
 # 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
@@ -29,23 +31,23 @@ checkapp() {
     # 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() {
@@ -60,19 +62,19 @@ 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() {
@@ -87,19 +89,19 @@ 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() {
@@ -111,21 +113,21 @@ 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===
@@ -134,15 +136,15 @@ displayMissing() {
     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===
@@ -151,15 +153,15 @@ displayMissing() {
     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===
@@ -185,21 +187,21 @@ checkInt() {
 
     #===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() {
@@ -214,8 +216,8 @@ 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
@@ -235,12 +237,12 @@ read_psarg() {
     # 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
@@ -261,6 +263,21 @@ find_flist() {
     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
@@ -268,7 +285,7 @@ main() {
     #        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;
@@ -280,45 +297,40 @@ main() {
     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
@@ -354,7 +366,7 @@ main() {
             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)";
@@ -365,7 +377,7 @@ main() {
     ### 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";