From: Steven Baltakatei Sandoval Date: Sun, 22 Sep 2024 06:47:13 +0000 (+0000) Subject: feat(user/rand_media_pl.sh):Script to play random position of media X-Git-Url: https://zdv2.bktei.com/gitweb/BK-2020-03.git/commitdiff_plain/b0f274d1f2b18d7d18ca86cbe612b3e37d2a7cdc?ds=sidebyside feat(user/rand_media_pl.sh):Script to play random position of media --- diff --git a/unitproc/bk_export_audio.sh b/unitproc/bk_export_audio.sh index 927556e..140a63d 100755 --- a/unitproc/bk_export_audio.sh +++ b/unitproc/bk_export_audio.sh @@ -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 index 0000000..a9c981b --- /dev/null +++ b/user/rand_media_pl.sh @@ -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."; +