feat(us/bk-copy-rand-music.sh):Add script
authorSteven Baltakatei Sandoval <baltakatei@gmail.com>
Wed, 19 Jan 2022 04:51:05 +0000 (04:51 +0000)
committerSteven Baltakatei Sandoval <baltakatei@gmail.com>
Wed, 19 Jan 2022 04:51:05 +0000 (04:51 +0000)
unitproc/bktemp-checkIsInArray [new file with mode: 0644]
unitproc/bktemp-check_parsable_audio_ffprobe [new file with mode: 0644]
unitproc/bktemp-get_audio_format [new file with mode: 0644]
unitproc/bktemp-get_media_length [new file with mode: 0644]
user/bk-copy-rand-music.sh [new file with mode: 0644]

diff --git a/unitproc/bktemp-checkIsInArray b/unitproc/bktemp-checkIsInArray
new file mode 100644 (file)
index 0000000..15d2fab
--- /dev/null
@@ -0,0 +1,82 @@
+#!/usr/bin/env bash
+
+yell() { echo "$0: $*" >&2; } # print script path and all args to stderr
+die() { yell "$*"; exit 111; } # same as yell() but non-zero exit status
+try() { "$@" || die "cannot $*"; } # runs args as command, reports args if command fails
+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 arg2 string_test
+    local -a 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
+
+# Sample test code
+my_array=("jan" "feb" "mar" "apr");
+yell "Array contains:${my_array[@]}";
+test_string="feb";
+yell "Checking to see if $test_string is in array...";
+if checkIsInArray "$test_string" "${my_array[@]}"; then
+    yell "\"$test_string\" is in array";
+else
+    yell "\"$test_string\" is not in array";
+fi;
+yell ""; # debug
+
+sleep 1;
+
+my_array=("jan" "feb" "mar" "apr");
+yell "Array contains:${my_array[@]}";
+test_string="oct";
+yell "Checking to see if $test_string is in array...";
+if checkIsInArray "$test_string" "${my_array[@]}"; then
+    yell "\"$test_string\" is in array";
+else
+    yell "\"$test_string\" is not in array";
+fi;
+yell ""; # debug
+
+sleep 1;
+
+my_array=("jan" "feb" "mar" "apr");
+yell "Array contains:${my_array[@]}";
+test_string="feb mar";
+yell "Checking to see if $test_string is in array...";
+if checkIsInArray "$test_string" "${my_array[@]}"; then
+    yell "\"$test_string\" is in array";
+else
+    yell "\"$test_string\" is not in array";
+fi;
+yell ""; # debug
diff --git a/unitproc/bktemp-check_parsable_audio_ffprobe b/unitproc/bktemp-check_parsable_audio_ffprobe
new file mode 100644 (file)
index 0000000..9230a3f
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env bash
+
+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
+
+# Author: Steven Baltakatei Sandoval
+# License: GPLv3+
diff --git a/unitproc/bktemp-get_audio_format b/unitproc/bktemp-get_audio_format
new file mode 100644 (file)
index 0000000..78d9cf6
--- /dev/null
@@ -0,0 +1,48 @@
+#!/usr/bin/env bash
+
+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
+
+# Author: Steven Baltakatei Sandoval
+# License: GPLv3+
\ No newline at end of file
diff --git a/unitproc/bktemp-get_media_length b/unitproc/bktemp-get_media_length
new file mode 100644 (file)
index 0000000..76c8f3d
--- /dev/null
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+
+get_media_length() {
+    # Use ffprobe to get media container length in seconds (float)
+    # Usage: get_media_length arg1
+    # Input:  arg1: path to file
+    # Output: stdout: seconds (float)
+    # Depends: ffprobe 4.1.8
+    # Ref/Attrib: [1] How to get video duration in seconds? https://superuser.com/a/945604
+    local file_in
+    file_in="$1";
+    if [[ ! -f $file_in ]]; then
+       die "ERROR:Not a file:$file_in";
+    fi;
+    ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$file_in";
+} # Get media container length in seconds via stdout
+
+# Author: Steven Baltakatei Sandoval
+# License: GPLv3+
\ No newline at end of file
diff --git a/user/bk-copy-rand-music.sh b/user/bk-copy-rand-music.sh
new file mode 100644 (file)
index 0000000..c326e41
--- /dev/null
@@ -0,0 +1,478 @@
+#!/usr/bin/env bash
+# Desc: Copies random music
+# Usage: bk-copy-rand-music.sh [dir SOURCE] [dir DEST] [int DURATION]
+
+declare -Ag appRollCall # Associative array for storing app status
+declare -Ag fileRollCall # Associative array for storing file status
+declare -Ag dirRollCall # Associative array for storing dir status
+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
+max_loops=1000000; # max number of files to test whether are audio or not
+
+yell() { echo "$0: $*" >&2; } # print script path and all args to stderr
+die() { yell "$*"; exit 111; } # same as yell() but non-zero exit status
+try() { "$@" || die "cannot $*"; } # runs args as command, reports args if command fails
+checkapp() {
+    # Desc: If arg is a command, save result in assoc array 'appRollCall'
+    # Usage: checkapp arg1 arg2 arg3 ...
+    # Version: 0.1.1
+    # Input: global assoc. array 'appRollCall'
+    # Output: adds/updates key(value) to global assoc array 'appRollCall'
+    # Depends: bash 5.0.3
+    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;
+    done;
+
+    #===Determine function return code===
+    if [ "$returnState" = "true" ]; then
+       return 0;
+    else
+       return 1;
+    fi;
+} # Check that app exists
+checkfile() {
+    # Desc: If arg is a file path, save result in assoc array 'fileRollCall'
+    # Usage: checkfile arg1 arg2 arg3 ...
+    # Version: 0.1.1
+    # Input: global assoc. array 'fileRollCall'
+    # Output: adds/updates key(value) to global assoc array 'fileRollCall';
+    # Output: returns 0 if app found, 1 otherwise
+    # Depends: bash 5.0.3
+    local returnState
+
+    #===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;
+    done;
+    
+    #===Determine function return code===
+    if [ "$returnState" = "true" ]; then
+       return 0;
+    else
+       return 1;
+    fi;
+} # Check that file exists
+checkdir() {
+    # Desc: If arg is a dir path, save result in assoc array 'dirRollCall'
+    # Usage: checkdir arg1 arg2 arg3 ...
+    # Version 0.1.2
+    # Input: global assoc. array 'dirRollCall'
+    # Output: adds/updates key(value) to global assoc array 'dirRollCall';
+    # Output: returns 0 if all args are dirs; 1 otherwise
+    # Depends: Bash 5.0.3
+    local returnState
+
+    #===Process Args===
+    for arg in "$@"; do
+       if [ -z "$arg" ]; then
+           dirRollCall["(Unspecified Dirname(s))"]="false"; returnState="false";
+       elif [ -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;
+    else
+       return 1;
+    fi
+} # Check that dir exists
+displayMissing() {
+    # Desc: Displays missing apps, files, and dirs
+    # Usage: displayMissing
+    # Version 1.0.0
+    # Input: associative arrays: appRollCall, fileRollCall, dirRollCall
+    # Output: stderr: messages indicating missing apps, file, or dirs
+    # Output: returns exit code 0 if nothing missing; 1 otherwise
+    # 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;
+    done;
+    if [ "$appMissing" = "true" ]; then  # Only indicate if an app is missing.
+       echo "$missingApps" 1>&2;
+    fi;
+    unset value;
+    #===END Display Missing Apps===
+
+    #===BEGIN Display Missing Files===
+    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;
+    done;
+    if [ "$fileMissing" = "true" ]; then  # Only indicate if an app is missing.
+       echo "$missingFiles" 1>&2;
+    fi;
+    unset value;
+    #===END Display Missing Files===
+
+    #===BEGIN Display Missing Directories===
+    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;
+    done;
+    if [ "$dirMissing" = "true" ]; then  # Only indicate if an dir is missing.
+       echo "$missingDirs" 1>&2;
+    fi;
+    unset value;
+    #===END Display Missing Directories===
+
+    #==END Display errors==
+    #==BEGIN Determine function return code===
+    if [ "$appMissing" == "true" ] || [ "$fileMissing" == "true" ] || [ "$dirMissing" == "true" ]; then
+       return 1;
+    else
+       return 0;
+    fi
+    #==END Determine function return code===
+} # Display missing apps, files, dirs
+showUsage() {
+    # Desc: Display script usage information
+    # Usage: showUsage
+    # Version 0.0.1
+    # Input: none
+    # Output: stdout
+    # Depends: GNU-coreutils 8.30 (cat)
+    cat <<'EOF'
+
+    DESCRIPTION:
+      This script may be used to copy a random selection of files from
+      SOURCE to DEST.
+
+    USAGE:
+      bk-copy-rand-music [dir SOURCE] [dir DEST] [int DURATION]
+
+    EXAMPLE:
+      bk-copy-rand-music ~/Music /tmp/music-sample 3600
+
+    DEPENDENCIES:
+      ffprobe
+      GNU Coreutils 8.30
+EOF
+} # Display information on how to use this script.
+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
+get_media_length() {
+    # Use ffprobe to get media container length in seconds (float)
+    # Usage: get_media_length arg1
+    # Input:  arg1: path to file
+    # Output: stdout: seconds (float)
+    # Depends: ffprobe 4.1.8
+    # Ref/Attrib: [1] How to get video duration in seconds? https://superuser.com/a/945604
+    local file_in
+    file_in="$1";
+    if [[ ! -f $file_in ]]; then
+       die "ERROR:Not a file:$file_in";
+    fi;
+    ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$file_in";
+} # Get media container length in seconds via stdout
+checkInt() {
+    # Desc: Checks if arg is integer
+    # Usage: checkInt arg
+    # Input: arg: integer
+    # Output: - return code 0 (if arg is integer)
+    #         - return code 1 (if arg is not integer)
+    # Example: if ! checkInt $arg; then echo "not int"; fi;
+    # Version: 0.0.1
+    local returnState
+
+    #===Process Arg===
+    if [[ $# -ne 1 ]]; then
+       die "ERROR:Invalid number of arguments:$#";
+    fi;
+    
+    RETEST1='^[0-9]+$'; # Regular Expression to test
+    if [[ ! $1 =~ $RETEST1 ]] ; then
+       returnState="false";
+    else
+       returnState="true";
+    fi;
+
+    #===Determine function return code===
+    if [ "$returnState" = "true" ]; then
+       return 0;
+    else
+       return 1;
+    fi;
+} # Checks if arg is integer
+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
+main() {
+    # Desc: Main program
+    # Input: arg1: path to source tree
+    #        arg2: path to destination tree
+    #        arg3: cumulative duration (seconds) of audio files in destination tree
+    #        assoc arrays: appRollCall, fileRollCall, dirRollCall
+    # Output: [none]
+    # Depends: yell(), checkdir() 0.1.2, displayMissing() 1.0.0, GNU Coreutils 8.30 (shuf)
+    local arg1 arg2 arg3 dur_dest dir_source dir_dest list_all
+    declare -a list_files # array for files to be considered
+    declare -A list_copy # assoc array for files to be copied (key=path; value=duration)
+
+    # Parse args
+    arg1="$1";
+    arg2="$2";
+    arg3="$3";
+    if [[ $# -ne 3 ]]; then showUsage; die "ERROR:Invalid number of args."; fi;
+
+    ## Check duration
+    if checkInt "$arg3"; then
+       dur_dest="$arg3";
+    else
+       yell "ERROR:Duration (seconds) not an int:$arg3"
+    fi;
+    
+    ## Check directories
+    if checkdir "$arg1" "$arg2"; then
+       dir_source="$arg1";
+       dir_dest="$arg2";
+    else
+       yell "ERROR:Directory error";
+    fi;
+
+    ## Check apps
+    checkapp ffprobe;
+
+    if ! displayMissing; then
+       showUsage;
+       die "ERROR:Check missing resources.";
+    fi;
+
+    yell "STATUS:Working...";
+    
+    # Generate file path list
+    list_all="$(find -L "$dir_source")";
+    #yell "DEBUG:list_files_rel:$list_files_rel";
+
+    # Prune list_all of non-files and save as array list_files
+    while read -r line; do
+       #yell "DEBUG:line:$line";
+       if ! [[ -f $line ]]; then
+           #yell "DEBUG:Not a file:$line";
+           #yell ""; # debug
+           continue;
+       fi;
+       list_files+=("$line");
+    done < <(echo "$list_all");
+
+    # Randomly test and add elements of list_files array to list_copy
+    dur=0; # Initialize duration
+    n=0; # Initialize loop counter
+    ## Get element count of list_files array
+    list_files_count="${#list_files[@]}";
+    while [[ $dur -le $dur_dest ]]; do
+       #yell "DEBUG:list_copy building loop:$n";
+       ### Select random element of list_files array
+       list_files_index="$(shuf -i 1-"$list_files_count" -n1)";
+       list_files_index="$((list_files_index - 1))"; # bash arrays are zero-indexed
+       path_candfile="${list_files[$list_files_index]}"; # path of candidate file
+
+       ### Check if has valid codec
+       if ! check_parsable_audio_ffprobe "$path_candfile"; then continue; fi; # reject
+       
+       ### Check if desired codec
+       file_format="$(get_audio_format "$path_candfile")";
+       if ! checkIsInArray "$file_format" "${music_codecs[@]}"; then continue; fi; # reject
+
+       ### Check and save duration
+       dur_cand="$(get_media_length "$path_candfile")";
+       dur_cand="${dur_cand%%.*}"; # convert float to int
+       if ! checkInt "$dur_cand"; then continue; fi; # reject
+
+       ### Add/update candfile to list_copy assoc. array (key=path; value=duration)
+       #yell "DEBUG:Adding $path_candfile";
+       list_copy["$path_candfile"]="$dur_cand";
+
+       ### Update total duration $dur by summing all list_copy assoc. array values
+       dur=0;
+       for value in "${list_copy[@]}"; do
+           dur="$((dur + value))";
+       done;
+       #yell "DEBUG:dur:$dur";
+
+       ### Sanity check
+       ((n++));
+       if [[ $n -gt $max_loops ]]; then die "ERROR:Too many loops:$n"; fi;
+    done;
+
+    # Copy files in list_copy to dir_dest;
+    for key in "${!list_copy[@]}"; do
+       value="${list_copy[$key]}";
+       ## Get basename of path
+       file_basename="$(basename "$key")";
+
+       ## Get 16-character b2sum fingerprint (for different files that share basename)
+       fingerprint="$(b2sum -l64 "$key" | cut -d' ' -f1)";
+
+       ## Form output path
+       path_output="$dir_dest"/"$fingerprint".."$file_basename";
+       
+       ## Copy
+       try cp "$key" "$path_output" && yell "NOTICE:Copied ($value seconds): $key ";
+       #yell "DEBUG:Copied $file_basename to $dur_dest.";
+
+       unset file_basename path_output
+    done;
+
+    # Report total duration
+    yell "NOTICE:Total duration (seconds):$dur";
+
+} # Main program
+
+main "$@";
+
+# Author: Steven Baltakatei Sandoval
+# License: GPLv3+