X-Git-Url: https://zdv2.bktei.com/gitweb/BK-2020-03.git/blobdiff_plain/8d35dc96ccee8a30e5e54873e319395a4c6bfe53..b9e8b771e985fcdf26ba8b9ccb8e31b62da757d3:/user/bk-copy-rand-music.sh?ds=sidebyside diff --git a/user/bk-copy-rand-music.sh b/user/bk-copy-rand-music.sh deleted file mode 100755 index ea384fe..0000000 --- a/user/bk-copy-rand-music.sh +++ /dev/null @@ -1,496 +0,0 @@ -#!/usr/bin/env bash -# Desc: Copies random audio files -# Usage: bk-copy-rand-music.sh [dir SOURCE] [dir DEST] [int DURATION] -# Version: 0.0.3 - -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 -max_filename_length="255"; # max output filename length -min_file_duration="10"; # minimum duration per music file - -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 containing - audio tracks 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 - if [[ "$dur_cand" -lt "$min_file_duration" ]]; 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; - - n=0; # Initialize loop counter - # 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 filename - file_name="$fingerprint".."$file_basename"; - file_name="${file_name:0:$max_filename_length}"; # Limit filename length (e.g. Windows has max of 255 characters) - - ## Form output path - path_output="$dir_dest"/"$file_name"; - - ## Copy - try cp "$key" "$path_output" && yell "NOTICE:Copied ($value seconds): $key "; - #yell "DEBUG:Copied $file_basename to $dur_dest."; - - ## Append log - path_log_output="$dir_dest"/COPY.log; - if [[ $n -le 0 ]]; then - echo "fingerprint","duration","original_path" >> "$path_log_output"; - else - echo "$fingerprint","$value","$key" >> "$path_log_output"; - fi; - - ((n++)); - unset file_basename path_output - done; - - # Report total duration - yell "NOTICE:Total duration (seconds):$dur"; - -} # Main program - -main "$@"; - -# Author: Steven Baltakatei Sandoval -# License: GPLv3+