#!/usr/bin/env bash
# Desc: Copies random audio files
# Usage: bk-copy-rand-music [dir SOURCE] [dir DEST] [int DURATION] ([int BYTES])
-# Version: 0.4.0
+# Version: 0.6.1
# Depends: BK-2020-03: bkshuf v0.1.0
declare -Ag appRollCall # Associative array for storing app status
max_filename_length="255"; # max output filename length
min_file_duration="30"; # minimum duration per music file
max_file_duration="3600"; # maximum duration per music file
-min_file_size="100000"; # minimum size per music file (bytes)
-max_file_size="100000000"; # maximum size per music file (bytes)
+min_file_size="100000"; # minimum size per input music file (bytes)
+max_file_size="100000000"; # maximum size per input music file (bytes)
siz_dest="600000000"; # default destination size limit: 600 MB
max_find_depth="10"; # max find depth
+limit_bitrate="256000"; # maximum bitrate (bps) for output audio files (256000 for opus)
# Load env vars (bkshuf defaults for typical music albums)
if [[ ! -v BKSHUF_PARAM_LINEC ]]; then export BKSHUF_PARAM_LINEC=1000000; fi;
# 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";
+
+ #yell "DEBUG:Finished get_media_length(). $(declare -p file_in)";
} # Get media container length in seconds via stdout
checkInt() {
# Desc: Checks if arg is integer
return 1;
fi;
} # Check if string is element in array
+get_media_bitrate() {
+ # Use ffprobe to get audio/visual media container bitrate (bits per second integer)
+ # Usage: get_media_bitrate arg1
+ # Input: arg1: str path to file
+ # Output: stdout: int bitrate (bits per second)
+ # Version: 0.0.1
+ # Depends: ffprobe 4.4.2
+ # Ref/Attrib: [1] How to get video duration in seconds? https://superuser.com/a/945604
+ # [2] Determine video bitrate using ffmpeg https://superuser.com/questions/1106343/determine-video-bitrate-using-ffmpeg
+ 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=bit_rate -of default=noprint_wrappers=1:nokey=1 "$file_in";
+
+ #yell "DEBUG:Finished get_media_bitrate with $? on:$1";
+} # Get media container length in seconds via stdout
+transcode_copy() {
+ # Desc: Transcode high bitrate file into smaller opus file
+ # Note: Meant for downsizing large lossless FLAC
+ # Input: arg1 str path to input audio file
+ # arg2 str output file path
+ # var limit_bitrate int max output transcode bitrate (bps)
+ # Output: file
+
+ ffmpeg -nostdin -i "$1" -c:a libopus -b:a "${limit_bitrate}" "${2}.opus";
+}; # transcode to lower bitrate audio file
+
main() {
# Desc: Main program
# Input: arg1: path to source tree
# Check env vars
if ! checkInt "$BKSHUF_PARAM_LINEC"; then
- die "FATAL:Not an int:BKSHUF_PARAM_LINEC:$BKSHUF_PARAM_LINEC"; fi;
+ die "FATAL:Not an int:BKSHUF_PARAM_LINEC:${BKSHUF_PARAM_LINEC}"; fi;
if ! checkInt "$BKSHUF_PARAM_GSIZE"; then
- die "FATAL:Not an int:BKSHUF_PARAM_LINEC:$BKSHUF_PARAM_GSIZE"; fi;
+ die "FATAL:Not an int:BKSHUF_PARAM_LINEC:${BKSHUF_PARAM_GSIZE}"; fi;
+
+ # Check adjustable parameters
+ if ! checkInt "$limit_bitrate"; then
+ die "FATAL:Not an int:limit_bitrate:${limit_bitrate}"; fi;
## Check duration
if checkInt "$arg3"; then
dur_dest="$arg3";
else
- die "FATAL:Duration (seconds) not an int:$arg3"
+ die "FATAL:Duration (seconds) not an int:${arg3}"
fi;
## Check size
if checkInt "$arg4"; then
siz_dest="$arg4";
else
- die "FATAL:Size (bytes) not an int:$arg4";
+ die "FATAL:Size (bytes) not an int:${arg4}";
fi;
fi;
path_candfile="$line"; # path of candidate file
- ### Check size
- siz_cand="$(du -b "$path_candfile" | awk '{ print $1 }')"; # size in bytes
+ ### Check duration
+ dur_cand="$(get_media_length "$path_candfile")"; # length in seconds (float)
+ dur_cand="${dur_cand%%.*}"; # convert float to int
+ if ! checkInt "$dur_cand"; then yell "STATUS:Not an int:dur_cand:${dur_cand}"; continue; fi; # reject
+ if [[ "$((dur + dur_cand))" -gt "$dur_dest" ]]; then yell "STATUS:Will put us over on time."; continue; fi; # reject
+ if [[ "$dur_cand" -lt "$min_file_duration" ]]; then yell "STATUS:Duration too short."; continue; fi; # reject
+ if [[ "$dur_cand" -gt "$max_file_duration" ]]; then yell "STATUS:Duration too long."; continue; fi; # reject
+
+ ### Check raw size
+ siz_cand_raw="$(du -Lb "$path_candfile" | awk '{ print $1 }')"; # size in bytes
+ if ! checkInt "$siz_cand_raw"; then continue; fi; # reject
+ if [[ "$siz_cand_raw" -lt "$min_file_size" ]]; then yell "STATUS:Input file too small."; continue; fi; # reject
+ if [[ "$siz_cand_raw" -gt "$max_file_size" ]]; then yell "STATUS:Input file too large."; continue; fi; # reject
+
+ ### Check effective size
+ bit_cand="$(get_media_bitrate "$path_candfile")"; # bitrate in bps
+ if ! checkInt "$bit_cand"; then die "FATAL:Unable to get bitrate of candidate file. $(declare -p bit_cand path_candfile)"; fi;
+ #### Take into account max output bitrate $limit_bitrate
+ if [[ "$bit_cand" -gt "$limit_bitrate" ]]; then bit_cand="$limit_bitrate"; fi; # assume output bitrate cap
+ siz_cand="$(( (bit_cand * dur_cand) / 8 ))"; # effective size in bytes
if ! checkInt "$siz_cand"; then continue; fi; # reject
if [[ "$((siz + siz_cand))" -gt "$siz_dest" ]]; then continue; fi; # reject
if [[ "$siz_cand" -lt "$min_file_size" ]]; then continue; fi; # reject
file_format="$(get_audio_format "$path_candfile")";
if ! checkIsInArray "$file_format" "${music_codecs[@]}"; then continue; fi; # reject
- ### Check 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 + dur_cand))" -gt "$dur_dest" ]]; then continue; fi; # reject
- if [[ "$dur_cand" -lt "$min_file_duration" ]]; then continue; fi; # reject
- if [[ "$dur_cand" -gt "$max_file_duration" ]]; then continue; fi; # reject
-
### Update stats digits widths
#### duration
dur_cand_wnow="$(printf "%s" "$dur_cand" | wc -m)"; # duration width count
# Copy files in list_copy to dir_dest;
while read -r line; do
#yell "DEBUG:line:$line"; # debug
- fdur="$(printf "%s" "$line" | cut -d',' -f1)";
- fsize="$(printf "%s" "$line" | cut -d',' -f2)";
+ fdur="$(printf "%s" "$line" | cut -d',' -f1)"; # duration in seconds (integer)
+ fsize="$(printf "%s" "$line" | cut -d',' -f2)"; # size in bytes
fpath="$(printf "%s" "$line" | cut -d',' -f3-)";
+ fbitrate="$(get_media_bitrate "$fpath")"; # bitrate in bps
+ if ! checkInt "$fbitrate"; then die "FATAL:Invalid bitrate. $(declare -p fpath fbitrate)"; fi;
## Get basename of path
file_basename="$(basename "$fpath")";
### Get basename without unprintable non-ASCII characters
path_output="$dir_dest"/"$file_name";
## Copy
- must cp "$fpath" "$path_output" && yell "NOTICE:Copied ($(printf "%""$dur_cand_w"d "$fdur") seconds): $fpath ";
- #yell "DEBUG:Copied $file_basename to $dur_dest.";
+ yell "DEBUG:$(declare -p fdur fsize fbitrate fpath file_basename file_basename_compat fingerprint num file_name path_output)";
+ if [[ "$fbitrate" -lt "$limit_bitrate" ]]; then
+ must cp "$fpath" "$path_output" && yell "NOTICE:Copied ($(printf "%""$dur_cand_w"d "$fdur") seconds): $fpath ";
+ yell "DEBUG:Copied ${file_basename} to ${dir_dest}.";
+ elif [[ "$fbitrate" -ge "$limit_bitrate" ]]; then
+ must transcode_copy "$fpath" "$path_output";
+ yell "DEBUG:Wrote ${limit_bitrate}kbps transcoded ${file_basename} to ${dir_dest}.";
+ fi;
+
## Append log
fpath_can="$(readlink -f "$fpath")"; # resolve symlinks to canonical path