break inexistence of this
This commit is contained in:
commit
02aece5e6e
337
play6
Executable file
337
play6
Executable file
|
@ -0,0 +1,337 @@
|
||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# The Playlist Jonkler
|
||||||
|
#
|
||||||
|
# usage: play6 <command> <playlist file>
|
||||||
|
# commands: tell: list files in playlist
|
||||||
|
# tell_w: list playlist entry weights
|
||||||
|
# tell_l: list playlist entry lengths
|
||||||
|
# gen: output shuffled playlist
|
||||||
|
# play: play music with mpv
|
||||||
|
#
|
||||||
|
# playlist file is shell commands.
|
||||||
|
# you shall run the `collect` command inside the playlist file.
|
||||||
|
#
|
||||||
|
# collect <weight> <file> [...]
|
||||||
|
#
|
||||||
|
# for example:
|
||||||
|
#
|
||||||
|
# collect \
|
||||||
|
# 100 epic_music.mp3 \
|
||||||
|
# 50 less_epic_music.webm
|
||||||
|
#
|
||||||
|
# if the playlist file does anything else, including, but not limited to:
|
||||||
|
#
|
||||||
|
# - executing commands that are not `collect`
|
||||||
|
# - setting any variable
|
||||||
|
# - referencing any variable
|
||||||
|
#
|
||||||
|
# the behavior is undefined.
|
||||||
|
#
|
||||||
|
# the program will assign a probability to each entry.
|
||||||
|
# the probability is proportional to the entry's weight and
|
||||||
|
# inversely proportional to the file's duration.
|
||||||
|
#
|
||||||
|
# the program will generate a shuffled playlist like this:
|
||||||
|
#
|
||||||
|
# 1. pick a random entry
|
||||||
|
# 2. if it's 1th last entry output, return to #1 with 100% probability
|
||||||
|
# (unless there is only one entry in the playlist)
|
||||||
|
# 3. if it's 2th last entry output, return to #1 with 91% probability
|
||||||
|
# 4. if it's 3th last entry output, return to #1 with 70% probability
|
||||||
|
# 5. output filename of chosen entry
|
||||||
|
#
|
||||||
|
# dependency : mpv (only for `play music with mpv`)
|
||||||
|
# ffprobe
|
||||||
|
# POSIX.1-2017 compatible system
|
||||||
|
# /dev/urandom or /dev/random
|
||||||
|
#
|
||||||
|
# this program is godawful and if it explodes
|
||||||
|
# its your fault for being foolish enough to use it
|
||||||
|
#
|
||||||
|
cd "$(dirname "$(command -v "$0")")" || exit
|
||||||
|
mktfifo() {
|
||||||
|
{ mkfifo "$(printf "$(test -z "$TMPDIR" && printf '/tmp' ||
|
||||||
|
printf "%s" "$TMPDIR")/pipe.$(rand).$(rand)$1" | tee /dev/fd/3)"; } 3>&1
|
||||||
|
}
|
||||||
|
if (printf '' | base64) >/dev/null 2>&1; then
|
||||||
|
_list_enc() {
|
||||||
|
printf :
|
||||||
|
base64 -w0
|
||||||
|
printf '\n'
|
||||||
|
}
|
||||||
|
_list_dec() {
|
||||||
|
printf '%s' "${1#:}" | base64 -d
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_list_enc() {
|
||||||
|
printf :
|
||||||
|
od -An -b | sed -e 's/[0-9][0-9]*/\\0\0/g;s/\s//g' | tr -d '\n'
|
||||||
|
printf '\n'
|
||||||
|
}
|
||||||
|
_list_dec() {
|
||||||
|
printf '%b' "$(printf '%s' "${1#:}" | base64 -d)"
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
list_mk() {
|
||||||
|
for _list_mk_arg; do
|
||||||
|
printf '%s' "$_list_mk_arg" | _list_enc
|
||||||
|
done
|
||||||
|
}
|
||||||
|
list_slice() {
|
||||||
|
[ $# = 2 ] || return 1
|
||||||
|
if [ $1 -gt $2 ]; then
|
||||||
|
list_slice $2 $1;
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
_list_slice_i=1
|
||||||
|
while read -r line; do
|
||||||
|
if test $_list_slice_i -gt $2; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
if test $_list_slice_i -ge $1; then
|
||||||
|
printf '%s\n' "$line"
|
||||||
|
fi
|
||||||
|
_list_slice_i=$((_list_slice_i+1))
|
||||||
|
done
|
||||||
|
unset _list_slice_i
|
||||||
|
}
|
||||||
|
list_len() {
|
||||||
|
tr -cd : | wc -c
|
||||||
|
}
|
||||||
|
list_get() {
|
||||||
|
list_slice $1 $1 | list_xargs printf '%s'
|
||||||
|
}
|
||||||
|
inarg() {
|
||||||
|
_inarg_arg="$1"
|
||||||
|
shift 1
|
||||||
|
printf '%s\n' "$(printf '%s' "$_inarg_arg")" | "$@"
|
||||||
|
unset _inarg_arg
|
||||||
|
}
|
||||||
|
list_xargs() {
|
||||||
|
while read -r _list_xargs_arg; do
|
||||||
|
_list_xargs_arg="$(_list_dec "$_list_xargs_arg"; echo :)"
|
||||||
|
_list_xargs_arg="${_list_xargs_arg%:}"
|
||||||
|
set -- "$@" "$_list_xargs_arg"
|
||||||
|
done
|
||||||
|
unset _list_xargs_arg
|
||||||
|
"$@"
|
||||||
|
}
|
||||||
|
list_pop() {
|
||||||
|
read -r _list_pop_line || return
|
||||||
|
inarg "$_list_pop_line" list_xargs printf '%s'
|
||||||
|
}
|
||||||
|
list_pack() {
|
||||||
|
[ $1 -ge 1 ] || return
|
||||||
|
_list_pack_spl=$1
|
||||||
|
while :; do
|
||||||
|
read -r line || break
|
||||||
|
set -- "$line"
|
||||||
|
i=$_list_pack_spl
|
||||||
|
while test $i -gt 1; do
|
||||||
|
read -r line || return
|
||||||
|
set -- "$@" "$line"
|
||||||
|
i=$((i - 1))
|
||||||
|
done
|
||||||
|
for line; do
|
||||||
|
printf '%s\n' "$line"
|
||||||
|
done | _list_enc || return
|
||||||
|
done
|
||||||
|
}
|
||||||
|
collect() {
|
||||||
|
_collect_fifo_lens="$(mktfifo)"
|
||||||
|
_collect_gen() {
|
||||||
|
i=0;
|
||||||
|
_collect_on() {
|
||||||
|
_name="$(inarg "$1" list_get 2; echo :)"; _name="${_name%:}"
|
||||||
|
}
|
||||||
|
{ while :; do
|
||||||
|
_name="$(list_pop && echo :)" || break
|
||||||
|
i=$((i+1)); (
|
||||||
|
_name="${_name%:}"
|
||||||
|
_name="$(inarg "$_name" list_get 2; echo :)"
|
||||||
|
_name="${_name%:}"
|
||||||
|
printf "%d %.0f\n" $i \
|
||||||
|
"$(if test -e "$_name"
|
||||||
|
then
|
||||||
|
ffprobe -loglevel 8 -of flat -show_entries format=duration "$_name" |
|
||||||
|
sed 's/^[^"]*"\([^"]*\)"$/\1/'
|
||||||
|
else
|
||||||
|
printf 'warn: "%s": file not found\n' "$_name" >&2
|
||||||
|
echo -1
|
||||||
|
fi)" ) &
|
||||||
|
done; wait; } | sort -n | sed 's/^[0-9]* *//' | while read line; do
|
||||||
|
list_mk "$line"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
list_mk "$@" | list_pack 2 | _collect_gen > "$_collect_fifo_lens" &
|
||||||
|
_collect_fn() {
|
||||||
|
for _arg; do
|
||||||
|
_weight="$(inarg "$_arg" list_get 1; echo :)"; _weight="${_weight%:}"
|
||||||
|
_name="$(inarg "$_arg" list_get 2; echo :)"; _name="${_name%:}"
|
||||||
|
_len="$(list_pop <&3)" || exit
|
||||||
|
if [ "$_len" -ge 0 ]; then
|
||||||
|
_newf="$(list_mk "$_name" "$_weight" "$_len"; echo :)";
|
||||||
|
_newf="${_newf%:}"; list_mk "$_newf"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
list="$(list_mk "$@" | list_pack 2 |
|
||||||
|
list_xargs _collect_fn 3<"$_collect_fifo_lens")"
|
||||||
|
rm "$_collect_fifo_lens"
|
||||||
|
unset _collect_fifo_lens
|
||||||
|
unset _collect_fn
|
||||||
|
unset _collect_gen
|
||||||
|
if test $(inarg "$list" list_len) = 0; then
|
||||||
|
printf 'fatal: no music\n' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
if test -e /dev/urandom; then
|
||||||
|
rand() { echo $(head -c4 /dev/urandom | od -An -t u); }
|
||||||
|
elif test -e /dev/random; then
|
||||||
|
rand() { echo $(head -c4 /dev/random | od -An -t u); }
|
||||||
|
else
|
||||||
|
printf 'fatal: no random\n' >&2;
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
randn() {
|
||||||
|
(while true; do
|
||||||
|
if test -z $1; then printf 'randn: bad argument\n' >&2; exit 1; fi
|
||||||
|
_rand=$(rand || exit)
|
||||||
|
if ! [ $_rand -lt $(((1<<32) % $1)) ]; then
|
||||||
|
break;
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
printf $(($_rand % $1)))
|
||||||
|
}
|
||||||
|
gen() {
|
||||||
|
(_f() {
|
||||||
|
_ff() {
|
||||||
|
printf '{\n\tname: %s\n\tweight: %sx\n\tlen: %ss\n}\n' "$@" >&2
|
||||||
|
}
|
||||||
|
for arg; do
|
||||||
|
inarg "$arg" list_xargs _ff
|
||||||
|
done
|
||||||
|
};inarg "$list" list_xargs _f)
|
||||||
|
_len="$(inarg "$list" list_len)"
|
||||||
|
echo count: $_len >&2
|
||||||
|
_maxll="$(_len_f() {
|
||||||
|
mll="$(inarg "$1" list_get 3)"
|
||||||
|
shift 1;
|
||||||
|
for arg; do
|
||||||
|
num="$(inarg "$arg" list_get 3)"
|
||||||
|
if [ "$num" -ge "$mll" ]; then
|
||||||
|
mll="$num"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo $mll
|
||||||
|
}; inarg "$list" list_xargs _len_f)"
|
||||||
|
echo maxlen: $_maxll >&2
|
||||||
|
_weight="$(wei=0
|
||||||
|
_onlist(){
|
||||||
|
for arg; do
|
||||||
|
_name="$(inarg "$arg" list_get 1; echo :)"; _name="${_name%:}"
|
||||||
|
_vwei="$(inarg "$arg" list_get 2; echo :)"; _vwei="${_vwei%:}"
|
||||||
|
_vlen="$(inarg "$arg" list_get 3; echo :)"; _vlen="${_vlen%:}"
|
||||||
|
prevwei=$wei;
|
||||||
|
wei=$((wei+(((10000 * _maxll) / _vlen) * _vwei)))
|
||||||
|
printf 'chance: %s\n' $((wei-prevwei)) >&2
|
||||||
|
_newf="$(list_mk "$_name" "$wei"; echo :)";
|
||||||
|
_newf="${_newf%:}"; list_mk "$_newf"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
inarg "$list" list_xargs _onlist
|
||||||
|
echo :)"; _weight="${_weight%:}"
|
||||||
|
maxwei="$(_mw_fn(){
|
||||||
|
shift $(($# - 1))
|
||||||
|
inarg "$1" list_get 2
|
||||||
|
}; inarg "$_weight" list_xargs _mw_fn)"
|
||||||
|
set -- "" "" ""
|
||||||
|
while true; do
|
||||||
|
num=$(randn $maxwei || exit)
|
||||||
|
line="$(_fn(){
|
||||||
|
for arg; do
|
||||||
|
lnd="$arg"
|
||||||
|
wei="$(inarg "$arg" list_get 2)"
|
||||||
|
if [ $num -lt "$wei" ]; then break; fi
|
||||||
|
done
|
||||||
|
inarg "$lnd" list_get 1
|
||||||
|
}; inarg "$_weight" list_xargs _fn; echo :)"
|
||||||
|
line="${line%:}"
|
||||||
|
if { test "$line" != "$3" || test $_len = 1; } &&
|
||||||
|
{ test "$line" != "$2" || test $(randn 100) -lt 9; } &&
|
||||||
|
{ test "$line" != "$1" || test $(randn 100) -lt 30; }
|
||||||
|
then
|
||||||
|
printf "%d\n%s\n" "$(printf '%s' "$line" | wc -c)" "$line" || exit
|
||||||
|
yes '' | tr '\n' ' ' | head -c 65536 # fill pipe quicker
|
||||||
|
fi
|
||||||
|
shift 1
|
||||||
|
set -- "$@" "$line"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
gen2() {
|
||||||
|
gen | while read len; do
|
||||||
|
dd bs=$len count=1 2>/dev/null; printf '\0'
|
||||||
|
dd bs=1 count=1 2>/dev/null >/dev/null
|
||||||
|
done
|
||||||
|
}
|
||||||
|
play() {
|
||||||
|
_fifo_playl="$(mktfifo .m3u8)"
|
||||||
|
_fifo_script="$(mktfifo .lua)"
|
||||||
|
{ cat <<'EOF'
|
||||||
|
local f = assert(io.open("/dev/fd/4"))
|
||||||
|
function spawn()
|
||||||
|
local sngi = assert(tonumber(f:read()))
|
||||||
|
local song = f:read(sngi)
|
||||||
|
f:read()
|
||||||
|
print("PLAYING: "..song)
|
||||||
|
mp.commandv("loadfile",song,"replace")
|
||||||
|
mp.set_property_bool("pause", false)
|
||||||
|
end
|
||||||
|
mp.observe_property("eof-reached", "bool", function(name, value)
|
||||||
|
if value then
|
||||||
|
spawn()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
spawn()
|
||||||
|
EOF
|
||||||
|
} >"$_fifo_script" &
|
||||||
|
gen >"$_fifo_playl" &
|
||||||
|
curpid=$$
|
||||||
|
clrn(){
|
||||||
|
rm "$_fifo_playl"
|
||||||
|
rm "$_fifo_script"
|
||||||
|
}
|
||||||
|
( while kill -0 $curpid 2>/dev/null; do sleep 0.05; done; clrn ) &
|
||||||
|
job="$(jobs -p | tail -n1)"
|
||||||
|
{
|
||||||
|
mpv --keep-open --no-video \
|
||||||
|
--volume=69 --script="$_fifo_script" --idle
|
||||||
|
} 4<"$_fifo_playl"
|
||||||
|
kill $job;
|
||||||
|
clrn
|
||||||
|
}
|
||||||
|
tell() { _tell_w_f() { for arg; do _name="$(inarg "$arg" list_get 1; echo :)"
|
||||||
|
_name="${_name%:}"; printf "%s\0" "$_name"; done }
|
||||||
|
inarg "$list" list_xargs _tell_w_f; }
|
||||||
|
tell_w() { _tell_w_f() { for arg; do _name="$(inarg "$arg" list_get 2; echo :)"
|
||||||
|
_name="${_name%:}"; printf "%s\0" "$_name"; done }
|
||||||
|
inarg "$list" list_xargs _tell_w_f; }
|
||||||
|
tell_l() { _tell_w_f() { for arg; do _name="$(inarg "$arg" list_get 3; echo :)"
|
||||||
|
_name="${_name%:}"; printf "%s\0" "$_name"; done }
|
||||||
|
inarg "$list" list_xargs _tell_w_f; }
|
||||||
|
|
||||||
|
. "$2" || exit
|
||||||
|
if test -z "$list"; then
|
||||||
|
printf 'fatal: bad playlist' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if test "$1" = tell; then tell;
|
||||||
|
elif test "$1" = tell_w; then tell_w;
|
||||||
|
elif test "$1" = tell_l; then tell_l;
|
||||||
|
elif test "$1" = gen; then gen2;
|
||||||
|
elif test "$1" = play; then play;
|
||||||
|
else printf "play6: invalid argument\n" >&2; exit 1
|
||||||
|
fi
|
Loading…
Reference in a new issue