feat(user/rand_media_pl.sh):Script to play random position of media
[BK-2020-03.git] / user / rand_media_pl.sh
1 #!/usr/bin/env bash
2 # Desc
3 # - Finds audio and video files with specified extensions up to a max depth of 8.
4 # - Uses ffprobe to measure their durations.
5 # - Creates a playlist starting at a random location within the total runtime.
6 # - Stores durations and file paths in a dotfile for faster subsequent runs.
7 # - Prompts the user whether to use the cached data or regenerate it.
8 # Usage: rand_media_pl.sh [DIR]
9 # Version: 0.0.1
10 # Dependencies: Bash 4, ffprobe, mpv
11
12 yell() { echo "$0: $*" >&2; } # print script path and all args to stderr
13 die() { yell "$*"; exit 111; } # same as yell() but non-zero exit status
14 must() { "$@" || die "cannot $*"; } # runs args as command, reports args if command fails
15
16 # Configurable variables
17 SEARCH_DIR="$1";
18 MAX_DEPTH=8;
19 EXTENSIONS=("*.flac" "*.mp3" "*.opus" "*.m4a" "*.m4b" "*.mp4" "*.mkv" "*.webm");
20 CACHE_FILE=".playlist_cache";
21
22 declare -gA file_durations;
23 declare total_duration;
24
25 # Function to prompt the user
26 prompt_yes_no() {
27 yell "STATUS:User input required.";
28 local prompt_message="$1"
29 local user_input
30 while true; do
31 read -rp "$prompt_message [y/n]: " user_input
32 case "$user_input" in
33 [Yy]*) return 0 ;;
34 [Nn]*) return 1 ;;
35 *) echo "Please answer yes or no." ;;
36 esac;
37 done;
38 };
39
40 # Function to find files with specified extensions
41 find_media_files() {
42 yell "STATUS:Finding media files.";
43 local find_cmd=("find" "-L" "$SEARCH_DIR" "-maxdepth" "$MAX_DEPTH" "-type" "f" "(");
44 #declare -p find_cmd 1>&2; # debug
45 for ext in "${EXTENSIONS[@]}"; do
46 find_cmd+=("-iname" "$ext" "-o");
47 #declare -p find_cmd 1>&2; # debug
48 done;
49 # Remove the last "-o" and add ")"
50 unset 'find_cmd[-1]';
51 find_cmd+=(")");
52 "${find_cmd[@]}";
53 };
54
55 # Function to generate the playlist cache
56 generate_playlist_cache() {
57 yell "Generating playlist cache. This may take a while..."
58 # Initialize or empty the cache file
59 if [[ ! -f "$CACHE_FILE" ]]; then
60 touch "$CACHE_FILE";
61 else
62 rm "$CACHE_FILE";
63 fi;
64
65 total_duration=0
66 while IFS= read -r file; do
67 # Get the duration using ffprobe
68 duration=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$file")
69 if [[ -n "$duration" ]]; then
70 # Append the file path and duration to the cache file
71 printf "%s|%s\n" "$file" "$duration" >> "$CACHE_FILE";
72 total_duration=$(echo "$total_duration + $duration" | bc);
73 fi;
74 done < <(find_media_files | sort);
75 total_duration_s="$(echo "scale=1; ${total_duration} / 1" | bc -l)";
76 total_duration_h="$(echo "scale=1; ${total_duration} / 3600" | bc -l)";
77
78 yell "Total duration of playlist: ${total_duration_s} seconds. (${total_duration_h} hours.)";
79 };
80
81 # Function to read the playlist cache
82 read_playlist_cache() {
83 # Input: file_durations assoc. array
84 # total_duration array
85 yell "STATUS:Reading playlist cache.";
86
87 total_duration=0
88 while IFS='|' read -r file duration; do
89 file_durations["$file"]="$duration"
90 total_duration=$(echo "$total_duration + $duration" | bc)
91 done < "$CACHE_FILE"
92 };
93
94 # Main script
95 if [[ -f "$CACHE_FILE" ]]; then
96 if prompt_yes_no "Playlist cache detected. Do you want to use it? (Faster)"; then
97 read_playlist_cache;
98 else
99 generate_playlist_cache;
100 read_playlist_cache;
101 fi;
102 else
103 generate_playlist_cache;
104 read_playlist_cache;
105 fi;
106
107 # Check if any files were found
108 if [[ ${#file_durations[@]} -eq 0 ]]; then
109 declare -p file_durations 1>&2;
110 die "FATAL:No media files found.";
111 fi
112
113 # Get sorted list of files
114 mapfile -t sorted_files < <(printf '%s\n' "${!file_durations[@]}" | sort)
115
116 # Generate a random starting point within the total duration
117 random_point=$(awk -v max="$total_duration" 'BEGIN{srand(); print rand()*max}')
118
119 # Find the file and offset corresponding to the random starting point
120 accumulated_duration=0
121 for file in "${sorted_files[@]}"; do
122 duration="${file_durations["$file"]}"
123 new_accumulated_duration=$(echo "$accumulated_duration + $duration" | bc)
124 if (( $(echo "$random_point < $new_accumulated_duration" | bc -l) )); then
125 offset=$(echo "$random_point - $accumulated_duration" | bc)
126 yell "Starting playback from $offset seconds into file: $file"
127
128 # Create a playlist file
129 playlist_file=$(mktemp)
130 printf '%s\n' "${sorted_files[@]}" > "$playlist_file"
131
132 # Start playback using mpv
133 mpv --playlist="$playlist_file" --start="$offset" \
134 --playlist-start=$(($(printf '%s\n' "${sorted_files[@]}" | grep -n "^$file$" | cut -d: -f1)-1))
135
136 # Securely delete the playlist file
137 if [[ -n "$playlist_file" && -f "$playlist_file" ]]; then
138 rm "$playlist_file"; exit 0;
139 else
140 die "FATAL: playlist_file is either unset or not a valid file, skipping deletion.";
141 fi;
142 fi
143 accumulated_duration="$new_accumulated_duration";
144 done
145
146 die "FATAL: Could not find file corresponding to random point.";
147