commit 02aece5e6e89e18b2a6cdaa17fbef590fd6ab4b9 Author: Kimapr Date: Tue Dec 17 23:25:02 2024 +0500 break inexistence of this diff --git a/play6 b/play6 new file mode 100755 index 0000000..6895b28 --- /dev/null +++ b/play6 @@ -0,0 +1,337 @@ +#!/bin/sh +# +# The Playlist Jonkler +# +# usage: play6 +# 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 [...] +# +# 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