feat(user/rand_media_pl.sh):Script to play random position of media
authorSteven Baltakatei Sandoval <baltakatei@gmail.com>
Sun, 22 Sep 2024 06:47:13 +0000 (06:47 +0000)
committerSteven Baltakatei Sandoval <baltakatei@gmail.com>
Sun, 22 Sep 2024 06:47:13 +0000 (06:47 +0000)
unitproc/bk_export_audio.sh
user/rand_media_pl.sh [new file with mode: 0755]

index 927556e4afc6e5e706e88a64851da60077030693..140a63dd8027dd974edd33225410af81bfe02b1d 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/bash
 # Desc: Extracts audio from video files
 # Usage: bk_export_audio.sh [input_dir] ([output_dir])
-# Version: 0.1.2
+# Version: 0.1.3
 # Depends: bash 5.1.16, GNU Coreutils (8.32)
 
 # Plumbing
@@ -311,7 +311,7 @@ main() {
     yell "DEBUG:dir_out:$dir_out";
     while read -r file; do
         yell "DEBUG:count_jobs:$(count_jobs)";
-        while [[ "$(count_jobs)" -ge $max_jobs ]]; do sleep 0.1; done; # limit jobs        
+        while [[ "$(count_jobs)" -ge $max_jobs ]]; do sleep 0.01s; done; # limit jobs
         job "$file" "$dir_out" &
     done < <(find "$dir_in" -type f);
 
diff --git a/user/rand_media_pl.sh b/user/rand_media_pl.sh
new file mode 100755 (executable)
index 0000000..a9c981b
--- /dev/null
@@ -0,0 +1,147 @@
+#!/usr/bin/env bash
+# Desc
+#   - Finds audio and video files with specified extensions up to a max depth of 8.
+#   - Uses ffprobe to measure their durations.
+#   - Creates a playlist starting at a random location within the total runtime.
+#   - Stores durations and file paths in a dotfile for faster subsequent runs.
+#   - Prompts the user whether to use the cached data or regenerate it.
+# Usage: rand_media_pl.sh [DIR]
+# Version: 0.0.1
+# Dependencies: Bash 4, ffprobe, mpv
+
+yell() { echo "$0: $*" >&2; } # print script path and all args to stderr
+die() { yell "$*"; exit 111; } # same as yell() but non-zero exit status
+must() { "$@" || die "cannot $*"; } # runs args as command, reports args if command fails
+
+# Configurable variables
+SEARCH_DIR="$1";
+MAX_DEPTH=8;
+EXTENSIONS=("*.flac" "*.mp3" "*.opus" "*.m4a" "*.m4b" "*.mp4" "*.mkv" "*.webm");
+CACHE_FILE=".playlist_cache";
+
+declare -gA file_durations;
+declare total_duration;
+
+# Function to prompt the user
+prompt_yes_no() {
+    yell "STATUS:User input required.";
+    local prompt_message="$1"
+    local user_input
+    while true; do
+        read -rp "$prompt_message [y/n]: " user_input
+        case "$user_input" in
+            [Yy]*) return 0 ;;
+            [Nn]*) return 1 ;;
+            *) echo "Please answer yes or no." ;;
+        esac;
+    done;
+};
+
+# 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" "(");
+    #declare -p find_cmd 1>&2; # debug
+    for ext in "${EXTENSIONS[@]}"; do
+        find_cmd+=("-iname" "$ext" "-o");
+        #declare -p find_cmd 1>&2; # debug
+    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
+    if [[ ! -f "$CACHE_FILE" ]]; then
+        touch "$CACHE_FILE";
+    else
+        rm "$CACHE_FILE";
+    fi;
+    
+    total_duration=0
+    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")
+        if [[ -n "$duration" ]]; 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);
+        fi;
+    done < <(find_media_files | sort);
+    total_duration_s="$(echo "scale=1; ${total_duration} / 1" | bc -l)";
+    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  assoc. array
+    #        total_duration  array
+    yell "STATUS:Reading playlist cache.";
+    
+    total_duration=0
+    while IFS='|' read -r file duration; do
+        file_durations["$file"]="$duration"
+        total_duration=$(echo "$total_duration + $duration" | bc)
+    done < "$CACHE_FILE"
+};
+
+# 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;
+    else
+        generate_playlist_cache;
+        read_playlist_cache;
+    fi;
+else
+    generate_playlist_cache;
+    read_playlist_cache;
+fi;
+
+# 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
+
+# Get sorted list of files
+mapfile -t sorted_files < <(printf '%s\n' "${!file_durations[@]}" | sort)
+
+# Generate a random starting point within the total duration
+random_point=$(awk -v max="$total_duration" 'BEGIN{srand(); print rand()*max}')
+
+# Find the file and offset corresponding to the random starting point
+accumulated_duration=0
+for file in "${sorted_files[@]}"; do
+    duration="${file_durations["$file"]}"
+    new_accumulated_duration=$(echo "$accumulated_duration + $duration" | bc)
+    if (( $(echo "$random_point < $new_accumulated_duration" | bc -l) )); then
+        offset=$(echo "$random_point - $accumulated_duration" | bc)
+        yell "Starting playback from $offset seconds into file: $file"
+
+        # Create a playlist file
+        playlist_file=$(mktemp)
+        printf '%s\n' "${sorted_files[@]}" > "$playlist_file"
+
+        # Start playback using mpv
+        mpv --playlist="$playlist_file" --start="$offset" \
+            --playlist-start=$(($(printf '%s\n' "${sorted_files[@]}" | grep -n "^$file$" | cut -d: -f1)-1))
+
+        # Securely delete the playlist file
+        if [[ -n "$playlist_file" && -f "$playlist_file" ]]; then
+            rm "$playlist_file"; exit 0;
+        else
+            die "FATAL: playlist_file is either unset or not a valid file, skipping deletion.";
+        fi;
+    fi
+    accumulated_duration="$new_accumulated_duration";
+done
+
+die "FATAL: Could not find file corresponding to random point.";
+