]> zdv2.bktei.com Git - BK-2020-03.git/blobdiff - user/rand_media_pl.sh
Merge branch 'develop' of https://zdv2.bktei.com/gitweb/BK-2020-03 into develop
[BK-2020-03.git] / user / rand_media_pl.sh
index ecf26daa2e04ed56048ad4e01e3c815fc5c3f26d..abe7766fd9685a0dae81e66145f118cdaf9d7327 100755 (executable)
@@ -7,8 +7,8 @@
 #   - Prompts the user whether to use the cached data or regenerate it.
 #   - Uses an EDL file to specify the starting offset for the first file.
 # Usage: rand_media_pl.sh [DIR]
 #   - Prompts the user whether to use the cached data or regenerate it.
 #   - Uses an EDL file to specify the starting offset for the first file.
 # Usage: rand_media_pl.sh [DIR]
-# Version: 0.0.4
-# Dependencies: Bash 4, ffprobe, mpv, bc, shuf
+# Version: 0.1.0
+# Dependencies: Bash 4, ffprobe, mpv, bc, shuf, GNU Parallel
 
 yell() { echo "$0: $*" >&2; } # Print script path and all args to stderr
 die() { yell "$*"; exit 111; } # Same as yell() but exits with code 111
 
 yell() { echo "$0: $*" >&2; } # Print script path and all args to stderr
 die() { yell "$*"; exit 111; } # Same as yell() but exits with code 111
@@ -16,70 +16,69 @@ must() { "$@" || die "cannot $*"; } # Runs args as command, reports args if comm
 
 # Configurable variables
 SEARCH_DIR="${1:-.}"  # Default to current directory if no argument is provided
 
 # Configurable variables
 SEARCH_DIR="${1:-.}"  # Default to current directory if no argument is provided
-MAX_DEPTH=8
-EXTENSIONS=("*.flac" "*.mp3" "*.opus" "*.m4a" "*.m4b" "*.mp4" "*.mkv" "*.webm")
-CACHE_FILE=".playlist_cache"
+MAX_DEPTH=8;
+EXTENSIONS=("*.flac" "*.mp3" "*.opus" "*.m4a" "*.m4b" "*.mp4" "*.mkv" "*.webm");
+CACHE_FILE=".playlist_cache";
 
 
-declare -gA file_durations
-declare total_duration
+declare -gA file_durations;
+declare total_duration;
 
 # Function to prompt the user
 prompt_yes_no() {
 
 # Function to prompt the user
 prompt_yes_no() {
-    yell "STATUS: User input required."
-    local prompt_message="$1"
-    local user_input
+    yell "STATUS: User input required.";
+    local prompt_message="$1";
+    local user_input;
     while true; do
     while true; do
-        read -rp "$prompt_message [y/n]: " user_input
+        read -rp "$prompt_message [y/n]: " user_input;
         case "$user_input" in
             [Yy]*) return 0 ;;
             [Nn]*) return 1 ;;
             *) echo "Please answer yes or no." ;;
         case "$user_input" in
             [Yy]*) return 0 ;;
             [Nn]*) return 1 ;;
             *) echo "Please answer yes or no." ;;
-        esac
-    done
-}
+        esac;
+    done;
+};
 
 # Function to find files with specified extensions
 find_media_files() {
 
 # Function to find files with specified extensions
 find_media_files() {
-    yell "STATUS: Finding media files."
-    local find_cmd=("find" "-L" "$SEARCH_DIR" "-maxdepth" "$MAX_DEPTH" "-type" "f" "(")
+    yell "STATUS: Finding media files.";
+    local find_cmd=("find" "-L" "$SEARCH_DIR" "-maxdepth" "$MAX_DEPTH" "-type" "f" "(");
     for ext in "${EXTENSIONS[@]}"; do
     for ext in "${EXTENSIONS[@]}"; do
-        find_cmd+=("-iname" "$ext" "-o")
-    done
-    # Remove the last "-o" and add ")"
-    unset 'find_cmd[-1]'
-    find_cmd+=(")")
-    "${find_cmd[@]}"
-}
+        find_cmd+=("-iname" "$ext" "-o");
+    done;
+    # Remove the last "-o" and add ")";
+    unset 'find_cmd[-1]';
+    find_cmd+=(")");
+    "${find_cmd[@]}";
+};
 
 # Function to generate the playlist cache
 generate_playlist_cache() {
     yell "Generating playlist cache. This may take a while..."
     # Initialize or empty the cache file
 
 # Function to generate the playlist cache
 generate_playlist_cache() {
     yell "Generating playlist cache. This may take a while..."
     # Initialize or empty the cache file
-    : > "$CACHE_FILE"  # Truncate or create the cache file
+    : > "$CACHE_FILE";  # Truncate or create the cache file
 
 
-    total_duration=0
+    total_duration=0;
     while IFS= read -r file; do
         # Get the duration using ffprobe
         duration=$(ffprobe -v error -show_entries format=duration \
     while IFS= read -r file; do
         # Get the duration using ffprobe
         duration=$(ffprobe -v error -show_entries format=duration \
-            -of default=noprint_wrappers=1:nokey=1 "$file")
-        declare -p file duration 1>&2  # Debugging statement
+            -of default=noprint_wrappers=1:nokey=1 "$file");
+        declare -p file duration 1>&2;  # Debugging statement
 
         # Validate the duration
         if [[ -n "$duration" && "$duration" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
             # Append the file path and duration to the cache file
 
         # Validate the duration
         if [[ -n "$duration" && "$duration" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
             # Append the file path and duration to the cache file
-            printf "%s|%s\n" "$file" "$duration" >> "$CACHE_FILE"
-            total_duration=$(echo "$total_duration + $duration" | bc)
+            printf "%s|%s\n" "$file" "$duration" >> "$CACHE_FILE";
+            total_duration=$(echo "$total_duration + $duration" | bc);
         else
         else
-            yell "WARNING: Invalid duration '$duration' for file '$file', skipping." 1>&2
-        fi
-    done < <(find_media_files | sort)
-
-    total_duration_s=$(printf "%.1f" "$total_duration")
-    total_duration_h=$(echo "scale=1; $total_duration / 3600" | bc -l)
+            yell "WARNING: Invalid duration '$duration' for file '$file', skipping." 1>&2;
+        fi;
+    done < <(find_media_files | parallel readlink -f '{}' | sort -u; );
 
 
-    yell "Total duration of playlist: ${total_duration_s} seconds (${total_duration_h} hours)."
-}
+    total_duration_s="$(printf "%.1f" "$total_duration"; )";
+    total_duration_h="$(echo "scale=1; $total_duration / 3600" | bc -l; )";
 
 
+    yell "Total duration of playlist: ${total_duration_s} seconds (${total_duration_h} hours).";
+};
 # Function to read the playlist cache
 read_playlist_cache() {
     # Input: file_durations associative array
 # Function to read the playlist cache
 read_playlist_cache() {
     # Input: file_durations associative array
@@ -97,56 +96,79 @@ read_playlist_cache() {
         else
             yell "WARNING: Invalid duration '$duration' for file '$file', skipping." 1>&2
         fi
         else
             yell "WARNING: Invalid duration '$duration' for file '$file', skipping." 1>&2
         fi
-    done < "$CACHE_FILE"
-}
+    done < "$CACHE_FILE";
+};
+# Function to get byte count of file name
+prepend_path_bc() {
+    # Desc: Prepends a file path with %[path bytecount]%
+    # Usage: prepend_filename_bc [FILE]
+    # Input:  arg1    str  file path
+    # Output: stdout  str  %[int path length]%path
+    # Example: 'foo.txt' yields '%7%foo.txt'
+    # Depends: GNU Coreutils 8.32 (for 'wc')
+    #          BK-2020-03: yell(), die(), must()
+    # Ref/Attrib: See “Syntax of mpv EDL files” https://github.com/mpv-player/mpv/blob/master/DOCS/edl-mpv.rst#syntax-of-mpv-edl-files
+    fin="$1";
+
+    if [[ ! -f "$fin" ]]; then
+        yell "WARNING:File does not exist:${fin}";
+    fi;
+
+    bytecount="$(printf "%s" "$fin" | wc -c; )";
+    re='[0-9]';
+    if [[ ! "$bytecount" =~ $re ]]; then
+        die "FATAL:Not an int:${bytecount}; $(declare -p fin bytecount)";
+    fi;
+    printf "%%%s%%%s" "$bytecount" "$fin";
+};
 
 # Main script
 if [[ -f "$CACHE_FILE" ]]; then
     if prompt_yes_no "Playlist cache detected. Do you want to use it? (Faster)"; then
 
 # Main script
 if [[ -f "$CACHE_FILE" ]]; then
     if prompt_yes_no "Playlist cache detected. Do you want to use it? (Faster)"; then
-        read_playlist_cache
+        read_playlist_cache;
     else
     else
-        generate_playlist_cache
-        read_playlist_cache
-    fi
+        generate_playlist_cache;
+        read_playlist_cache;
+    fi;
 else
 else
-    generate_playlist_cache
-    read_playlist_cache
-fi
+    generate_playlist_cache;
+    read_playlist_cache;
+fi;
 
 # Check if any files were found
 if [[ ${#file_durations[@]} -eq 0 ]]; then
 
 # Check if any files were found
 if [[ ${#file_durations[@]} -eq 0 ]]; then
-    declare -p file_durations 1>&2
-    die "FATAL: No media files found."
-fi
+    declare -p file_durations 1>&2;
+    die "FATAL: No media files found.";
+fi;
 
 # Ensure total_duration is not empty
 if [[ -z "$total_duration" ]]; then
 
 # Ensure total_duration is not empty
 if [[ -z "$total_duration" ]]; then
-    die "FATAL: total_duration is empty."
-fi
+    die "FATAL: total_duration is empty.";
+fi;
 
 # Convert total_duration to an integer
 
 # Convert total_duration to an integer
-total_duration_int=$(printf "%.0f" "$total_duration")
+total_duration_int=$(printf "%.0f" "$total_duration");
 
 # Check if total_duration_int is a valid integer
 if ! [[ "$total_duration_int" =~ ^[0-9]+$ ]]; then
 
 # Check if total_duration_int is a valid integer
 if ! [[ "$total_duration_int" =~ ^[0-9]+$ ]]; then
-    die "FATAL: total_duration_int is not a valid integer."
-fi
+    die "FATAL: total_duration_int is not a valid integer.";
+fi;
 
 # Generate a random integer between 0 and total_duration_int - 1
 random_point=$(shuf -i 0-$((total_duration_int - 1)) -n1)
 
 # Generate a random integer between 0 and total_duration_int - 1
 random_point=$(shuf -i 0-$((total_duration_int - 1)) -n1)
-yell "DEBUG: total_duration=$total_duration, total_duration_int=$total_duration_int, random_point=$random_point" 1>&2
+yell "DEBUG: total_duration=$total_duration, total_duration_int=$total_duration_int, random_point=$random_point" 1>&2;
 
 # Get sorted list of files
 
 # Get sorted list of files
-mapfile -t sorted_files < <(printf '%s\n' "${!file_durations[@]}" | sort)
+mapfile -t sorted_files < <(printf '%s\n' "${!file_durations[@]}" | sort; );
 
 # Find the file and offset corresponding to the random starting point
 
 # Find the file and offset corresponding to the random starting point
-accumulated_duration=0
-accumulated_duration_int=0
+accumulated_duration=0;
+accumulated_duration_int=0;
 
 for idx in "${!sorted_files[@]}"; do
 
 for idx in "${!sorted_files[@]}"; do
-    file="${sorted_files[$idx]}"
-    duration="${file_durations["$file"]}"
-    declare -p idx file duration accumulated_duration 1>&2  # Debugging statement
+    file="${sorted_files[$idx]}";
+    duration="${file_durations["$file"]}";
+    declare -p idx file duration accumulated_duration 1>&2;  # Debugging statement
 
     # Validate the duration
     if [[ -n "$duration" && "$duration" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
 
     # Validate the duration
     if [[ -n "$duration" && "$duration" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
@@ -160,37 +182,40 @@ for idx in "${!sorted_files[@]}"; do
     fi
 
     if (( random_point < new_accumulated_duration_int )); then
     fi
 
     if (( random_point < new_accumulated_duration_int )); then
-        offset=$(echo "$random_point - $accumulated_duration_int" | bc)
-        yell "Starting playback from $offset seconds into file: $file"
+        offset=$(echo "$random_point - $accumulated_duration_int" | bc; );
+        yell "Starting playback from $offset seconds into file: $file";
 
         # Create an EDL file
 
         # Create an EDL file
-        edl_file=$(mktemp)
-        echo "# mpv EDL v0" > "$edl_file"
-
-        # First line: file with offset
-        printf '%s,%s\n' "$file" "$offset" >> "$edl_file"
+        edl_file=$(mktemp);
+        yell "DEBUG:EDL file at:${edl_file}"; # debug
+        echo "# mpv EDL v0" > "$edl_file";
+        
+        # Add first file to start playback at random offset position
+        file_bc="$(prepend_path_bc "$file")"; # See https://github.com/mpv-player/mpv/blob/master/DOCS/edl-mpv.rst#syntax-of-mpv-edl-files
+        printf '%s,%s\n' "$file_bc" "$offset" >> "$edl_file";
 
         # Append the rest of the files
         declare -p file offset idx sorted_files edl_file 1>&2; # debug
         for (( i=idx+1; i<${#sorted_files[@]}; i++ )); do
 
         # Append the rest of the files
         declare -p file offset idx sorted_files edl_file 1>&2; # debug
         for (( i=idx+1; i<${#sorted_files[@]}; i++ )); do
-            next_file="${sorted_files[$i]}"
+            next_file="${sorted_files[$i]}";
+            next_file_bc="$(prepend_path_bc "$next_file")";
             yell "STATUS:Adding:$next_file";
             yell "STATUS:Adding:$next_file";
-            printf '%s\n' "$next_file" >> "$edl_file"
-        done
+            printf '%s\n' "$next_file_bc" >> "$edl_file";
+        done;
 
         # Start playback using mpv
 
         # Start playback using mpv
-        mpv "$edl_file"
+        mpv "$edl_file" || exit 1;
 
         # Securely delete the EDL file
         if [[ -n "$edl_file" && -f "$edl_file" ]]; then
 
         # Securely delete the EDL file
         if [[ -n "$edl_file" && -f "$edl_file" ]]; then
-            rm "$edl_file"
-            exit 0
+            rm "$edl_file";
+            exit 0;
         else
         else
-            die "FATAL: edl_file is either unset or not a valid file, skipping deletion."
-        fi
-    fi
-    accumulated_duration="$new_accumulated_duration"
-    accumulated_duration_int="$new_accumulated_duration_int"
-done
-
-die "FATAL: Could not find file corresponding to random point."
+            die "FATAL: edl_file is either unset or not a valid file, skipping deletion.";
+        fi;
+    fi;
+    accumulated_duration="$new_accumulated_duration";
+    accumulated_duration_int="$new_accumulated_duration_int";
+done;
+
+die "FATAL: Could not find file corresponding to random point.";