Skip to content

Instantly share code, notes, and snippets.

@Urpagin
Created August 30, 2025 15:36
Show Gist options
  • Select an option

  • Save Urpagin/aff93fe938b40d0dfbc38c5bf89ad62f to your computer and use it in GitHub Desktop.

Select an option

Save Urpagin/aff93fe938b40d0dfbc38c5bf89ad62f to your computer and use it in GitHub Desktop.
Bash script that plays a random media file.
#!/usr/bin/env bash
# Author: Urpagin
# Date: 2025-08-30
# Description: Plays a random audio/video file given a directory.
# Default directory is cwd.
# Licence: MIT
set -euo pipefail
# Exits the program gracefully following user manual termination signal.
_handle_ctrlc() {
printf '\nUser termination detected; exiting...\n'
exit 130
}
trap '_handle_ctrlc' SIGINT
# All the required commands.
# If one is missing, the script will NOT continue.
readonly REQ_CMDS=( file shuf echo printf getopts realpath )
_check_cmds() {
for cmd in "${REQ_CMDS[@]}"; do
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "$cmd is required but not installed" >&2
exit 1
fi
done
}
# Exit if a required command is missing.
_check_cmds
_usage() {
cat <<EOF
Usage:
$(basename "$0") [OPTIONS] ENTRY...
Options:
-a Select audio
-v Select video
-l Loops over selected files, infinitely
-A Force player to be the same as audio files for all files
-h Show this help message
ENTRY:
Files and directories.
DESCRIPTION:
Plays a random audio/video file.
BEHAVIOUR:
- Recursively traverses passed in entries.
- By default, -av flags are specified and selects from the current directory.
The base command is equivalent to: $(basename "$0") -av .
Examples:
Plays a random audio or video file from the current directory (recursive):
$(basename "$0")
Plays a random audio or video file and selects it from ~/Music/ and ~/Videos/:
$(basename "$0") -av ~/Music/ ~/Videos/
Plays a random audio file:
$(basename "$0") -a ~/Music/
Plays a random video file:
$(basename "$0") -v ~/Videos/
Plays videos using the audio player (no video, only audio):
$(basename "$0") -A ~/Videos/
Be sure no video pops up:
$(basename "$0") -aA ~/Music/
Plays random video files in the user directory:
$(basename "$0") -vl ~
EOF
}
sel_audio=false
do_use_audio_player=false
sel_video=false
is_looping=false
# Thanks: https://flokoe.github.io/bash-hackers-wiki/howto/getopts_tutorial/#calling-it-with-option-arguments
while getopts ':aAvlh' opt; do
case "$opt" in
a)
sel_audio=true
;;
A)
do_use_audio_player=true
;;
v)
sel_video=true
;;
l)
is_looping=true
;;
h)
_usage
exit 0
;;
\?)
echo "Invalid options -${OPTARG}" >&2
_usage
exit 1
;;
esac
done
# Shift parsed arguments.
shift $((OPTIND - 1))
# global variable.
# We'll cache the output of find in here.
cached_find=''
# Thanks: https://unix.stackexchange.com/questions/217628/cut-string-on-last-delimiter
# Builds the cache of found files: cached_find
# Input: mass args $@
# Returns: nothing.
_build_cache() {
# for optimization, put this outside of this function.
# But laziness got the better of me.
local regex_sel
regex_sel='audio|video' # default case
# -av is by default, no need to add an if for it.
[[ $sel_audio = true && $sel_video = false ]] && regex_sel='audio'
[[ $sel_audio = false && $sel_video = true ]] && regex_sel='video'
if [[ -n "$cached_find" ]]; then
return
fi
# Use fd if it exist, for faster lookups.
echo "Finding your files..."
if command -v fd >/dev/null 2>&1; then
cached_find="$(fd -HI --type f "$@" \
-x file --mime-type {} \
| grep -E "$regex_sel" \
| cut -d: -f1)"
else
cached_find="$(find "$@" \
-type f \
-exec file --mime-type {} + \
| grep -E "$regex_sel" \
| rev \
| cut -d':' -f2- \
| rev)"
fi
echo "Found $(printf '%s\n' "$cached_find" | wc -l) files!"
}
# Input: File path in $1
_play_file() {
local f=${1:-}
[[ -z $f ]] && { echo "No file found" >&2; exit 1; }
printf "Now playing:\n%s\n" "$(realpath --relative-to="$PWD" "$f")"
# If audio file.
if file --mime-type "$f" | grep -q -E 'audio' || [[ "$do_use_audio_player" = true ]]; then
# Audio: no window, quiet, predictable exit
mpv --no-video --gapless-audio=no \
--idle=no --keep-open=no --msg-level=all=warn -- "$f"
else
# Video: fullscreen, HW decode, quiet, predictable exit
mpv --fs --hwdec=auto-safe \
--idle=no --keep-open=no --msg-level=all=warn -- "$f"
fi
}
# Plays a random file from mass arguments.
# Input: mass args $@
_playrandom() {
# Build the find cache.
_build_cache "$@"
# Select a random file
local selected
selected=$(echo "$cached_find" | shuf -n 1)
if [[ -z "$selected" ]]; then
echo "No matching files found." >&2
exit 1
fi
# Play it
_play_file "$selected"
}
# Input: mass args $@
_main() {
# No args = cwd
if [[ "$#" -eq 0 ]]; then
# Unset all args and set them to '.', so $1 = .
set -- '.'
fi
is_first=true
while [[ "$is_looping" = true || "$is_first" = true ]]; do
is_first=false
_playrandom "$@"
done
}
_main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment