-
-
Save dreamcat4/8e6c06a6bcfbd3f0f03c13b5603000db to your computer and use it in GitHub Desktop.
#!/bin/sh | |
# | |
# aud-jumptofile <file> | |
# | |
# audacious - cmdline helper script - Jump directly to mp3 file or radio station, if its already in 'Library' or 'Radio' playlists | |
# | |
# This script finds which track to play within the currently playing playlist | |
# instead of what audacious normally does, which is either creating a brand new playlist | |
# or appending the same track multiple times to the end of the currently playing playlist | |
# | |
# This script also includes instructions how to get an equivalent functionality as the win10 | |
# start menu, for direct and immediate playing of mp3s. By configuring this script to be launched | |
# by the tool 'albert' on linux. Or any other program which respects MIME types (such as 'xdg-open') | |
# | |
# Author: Dreamcat4 (2018) dreamcat4@gmail.com | |
# https://gist.github.com/dreamcat4/8e6c06a6bcfbd3f0f03c13b5603000db | |
# | |
# License Type: Apache 2.0 | |
# | |
set -x | |
# Requirements: | |
# ====================================================================== | |
# | |
# Requires audacious music player with: | |
# * a playlist named 'Library', for your mp3, m4a, and aac music files | |
# * a playlist named 'Radio', for your m3u internet stations | |
# Installation instructions: | |
# ====================================================================== | |
# | |
# * Make this script executable, and put this script somewhere on your $PATH | |
# * Set up mime types associations, as shown below. And reboot your system | |
# * Install the daemon with the command: $ sudo aud-jumptofile -i | |
# * Cache your library with the command: $ aud-jumptofile -u | |
# * Close and re-open audacious. Double check Settings --> Playlist --> Custom String: | |
# It MUST say: ${file-path}/${file-name} | ${artist} | |
# | |
# (optional, but highly recommended) | |
# * Configure the linux program 'albert', to play mp3s or switch radio stations | |
# * Make sure albert is configured with the 'Files' extension enabled | |
# * Add there the Path to your Music Library folder | |
# * Check the box to enable indexing of 'Audio' MIME types | |
# * Click 'Advanced' button for mime types, and select 'audio/*' | |
# * ALSO BE SURE TO CHECK 'Index Hidden Files', for radio station .m3us | |
# because we save the radio .m3u files to ~/Music/.radio_m3us | |
# Creating mime type associations, on gnome / ubuntu | |
# ====================================================================== | |
# Example .desktop file | |
# ~/.local/share/applications/aud-jumptofile.desktop: | |
# | |
# [Desktop Entry] | |
# Type=Application | |
# Exec=aud-jumptofile -o %f | |
# Hidden=false | |
# NoDisplay=false | |
# X-GNOME-Autostart-enabled=true | |
# Name[en_GB]=Aud Jump To File | |
# Name=Aud Jump To File | |
# Comment[en_GB]=In the currently playing playlist | |
# Comment=In the currently playing playlist | |
# | |
# Example mime types | |
# ~/.config/mimeapps.list: | |
# | |
# [Default Applications] | |
# audio/m3u=aud-jumptofile.desktop | |
# audio/x-mpegurl=aud-jumptofile.desktop | |
# audio/m4a=aud-jumptofile.desktop | |
# audio/mpeg=aud-jumptofile.desktop | |
# audio/x-mpegurl=aud-jumptofile.desktop | |
# audio/x-scpls=aud-jumptofile.desktop | |
# audio/x-vorbis+ogg=aud-jumptofile.desktop | |
# audio/x-wav=aud-jumptofile.desktop | |
# | |
# Check to see your MIME types are recognised properly, the litmus test: | |
# open <file> | |
# | |
# Script Settings | |
# ====================================================================== | |
# | |
# BE AWARE THAT THESE ARE ALSO HARD-CODED INTO THE INLINED DAEMON SCRIPT FURTHER DOWN | |
# SO HAVE TO MATCH THE SAME VARIABLES OVER THERE | |
# | |
# name of the program which is periodically retreiving and saving a textual copy of the currently playing playlist | |
_daemon="aud-fplcacher" | |
# | |
_cached_playlist_library="$HOME/.cache/audacious/library.cached_playlist" | |
_cached_playlist_radio="$HOME/.cache/audacious/radio.cached_playlist" | |
_radio_m3us="$HOME/Music/.radio_m3us" | |
# | |
# how often to update the file where the currently playing playlist is being cached | |
_daemon_update_interval=1000 | |
# | |
# the number of file path components to match against, from right to left. so that non-matching top level folders are ignored | |
_num_path_components=3 | |
# | |
# kill audtool playlist-display if audtool has been stuck gathering a playlist for more than this number seconds | |
lastActivationTimeoutSeconds=30 | |
# | |
# | |
# | |
_program="$(basename $0)" | |
err() | |
{ | |
_rc="$1" | |
shift | |
while [ "$1" ]; do | |
echo "${_program}: error: $1" | |
shift | |
done | |
exit $_rc | |
} | |
warn() | |
{ | |
echo "${_program}: warning: $@" | |
} | |
info() | |
{ | |
echo "${_program}: info: $@" | |
} | |
_cat_daemon() | |
{ | |
cat <<- "EOF" | |
#!/bin/sh | |
# | |
# audacious-jumptofile playlist file cacher | |
# re-reads the audacious playlist into a text file every few seconds | |
# | |
# This is a background task for my other script 'aud-jumptofile' | |
# It is required to put both scripts on your $PATH. | |
# | |
# Author: Dreamcat4 (2018) dreamcat4@gmail.com | |
# License Type: Apache 2.0 | |
if [ "$1" ]; then | |
_timeout="$1" | |
else | |
_timeout="1000" | |
fi | |
_cached_playlist_library="$HOME/.cache/audacious/library.cached_playlist" | |
_cached_playlist_radio="$HOME/.cache/audacious/radio.cached_playlist" | |
_radio_m3us="$HOME/Music/.radio_m3us" | |
mkdir -p ~/.cache/audacious | |
# configure the setting --> playlists --> title bar display | |
sed -i -e 's#generic_title_format=.*#generic_title_format=${file-path}/${file-name} | ${artist}#g' "$HOME/.config/audacious/config" | |
_radio_create_m3us() | |
{ | |
mkdir -p "$_radio_m3us" | |
cd "$_radio_m3us" | |
cat "$_cached_playlist_radio" | while read _line; do | |
_radio_name="$(echo "$_line" | sed -e "s/^.* | //g" -e "s/ | .*$//g")" | |
_radio_url="$(echo "$_line" | grep -o "http[^|]*" | sed -e "s/ *$//g")" | |
_m3u_matches="$(grep -l "^${_radio_url}$" * 2> /dev/null)" | |
if [ "$_m3u_matches" != "${_radio_name}.m3u" ]; then | |
# remove old version(s) | |
echo "$_m3u_matches" | while read _m3u; do | |
rm -f "$_m3u" | |
done | |
if [ ! "$_radio_name" ]; then | |
_radio_name="$(echo "$_radio_url" | sed -e "s|http.*//||g" -e "s|/||g")" | |
fi | |
# write a new .m3u file | |
echo "$_radio_url" > "${_radio_name}.m3u" | |
fi | |
done | |
} | |
_cache_playlist() | |
{ | |
_current_playlist_name="$(audtool current-playlist-name)" | |
case "$_current_playlist_name" in | |
Library) | |
audtool playlist-display | grep -o -E ".*~/Music[^|]*" > "$_cached_playlist_library" | |
;; | |
Radio) | |
audtool playlist-display | grep -o -E ".*http[^|]* \| [^|]*" | sed -e "s/\ *$//g" > "$_cached_playlist_radio" | |
_radio_create_m3us; | |
;; | |
*) | |
;; | |
esac | |
} | |
while true; do | |
if audtool playback-stopped || audtool playback-paused; then | |
_num_pl="$(audtool number-of-playlists)" | |
_pl_orig="$(audtool current-playlist)" | |
i=1 | |
while [ "$i" -le "$_num_pl" ]; do | |
audtool set-current-playlist $i | |
_cache_playlist; | |
i="$(expr $i + 1)" | |
done | |
audtool set-current-playlist $_pl_orig | |
else | |
_cache_playlist; | |
fi | |
sleep $_timeout | |
done | |
EOF | |
} | |
_install_daemon() | |
{ | |
if [ ! "$(id -u)" = "0" ]; then | |
err 1 "operation requires elevated permissions" | |
fi | |
_old_daemon="$(command -v "$_daemon")" | |
if [ ! "$_old_daemon" ]; then | |
_try_install=true | |
elif [ "$_old_daemon" ] && [ "$_force_install_daemon" ]; then | |
_try_install=true | |
else | |
err 1 "daemon already installed at \"$_old_daemon\"" \ | |
"run again with -f flag reinstall and force overwrite / removal" | |
fi | |
if [ "$_try_install" ]; then | |
info "installing the daemon \"$_daemon\"..." | |
_bin="$(echo $PATH | grep -o '/usr/local/bin')" | |
if [ ! "$_bin" ]; then | |
_bin="$(echo $PATH | grep -o '/usr/bin')" | |
fi | |
_binpath="${_bin}/${_daemon}" | |
touch "${_bin}/${_daemon}" | |
if [ ! -e "$_binpath" ]; then | |
err 1 "cannot write to destination folder \"$_bin\"" | |
fi | |
if [ "$_old_daemon" ]; then | |
rm "$_old_daemon" | |
fi | |
if [ -e "$_old_daemon" ]; then | |
err 1 "cannot remove previous daemon at \"$_old_daemon\"" | |
fi | |
_cat_daemon > "$_binpath" | |
chmod +x "$_binpath" | |
_new_daemon="$(command -v "$_daemon")" | |
if [ "$_new_daemon" ]; then | |
info "daemon installed at \"$_new_daemon\"" | |
exit 0 | |
else | |
err 1 "new daemon installed, but not executable at \"$_new_daemon\"" | |
fi | |
fi | |
} | |
_check_deps() | |
{ | |
_deps="audacious audtool ps killall expr grep wc shuf sed cut" | |
for _prog in $_deps; do | |
if [ ! "$(command -v "$_prog")" ]; then | |
err 1 "the required program dependancy \"$_prog\" is missing from your \$PATH | |
The current \$PATH is: | |
$PATH | |
" | |
fi | |
done | |
} | |
_kill_daemon() { | |
killall -q "$_daemon" | |
killall -q "audtool" | |
for _cache_file in "$_cached_playlist_library" "$_cached_playlist_radio"; do | |
if [ -e "$_cache_file" ]; then | |
rm "$_cache_file" | |
fi | |
done | |
} | |
_cat_help() | |
{ | |
cat <<- EOF | |
usage: | |
$_program [options] <file> | |
Where <file> is a single audio file, playable in audacious | |
options: | |
--only-in-playlist, -o | |
Only play the file specified if it was found in Library or Radio playlist | |
--update-playlist-cache, -u | |
Update the cached lookup entries right now, for Library & Radio playlists | |
--install-daemon, -i, [-f] | |
Install the background runner program into your \$PATH | |
--help, -h: | |
Display this message and exit | |
EOF | |
} | |
_parse_args() | |
{ | |
while [ "$1" ]; do | |
arg="$1" | |
case $arg in | |
--install-daemon|-i) _install_daemon=true ;; | |
-f) _force_install_daemon=true ;; | |
--update-playlist-cache|-u) _update_playlist_cache=true ;; | |
--only-in-playlist|-o) _only_in_playlist=true ;; | |
--help|-h) _help=true ;; | |
*) _file="$1" ;; | |
esac | |
shift | |
done | |
if [ "$_help" ]; then | |
_cat_help | |
exit 0 | |
fi | |
if [ "$_install_daemon" ]; then | |
_install_daemon | |
elif [ ! "$(command -v "$_daemon")" ]; then | |
err 1 "daemon not found on you \$PATH... you cannot continue" \ | |
"run with -i flag to install the daemon program, \"$_daemon\"" | |
fi | |
if [ "$_update_playlist_cache" ]; then | |
_killall=true | |
fi | |
if [ "$_file" ]; then | |
_file="$(realpath "$_file")" | |
_file_noext="$(echo "${_file%.*}")" | |
elif [ ! "$_killall" ]; then | |
warn "no filename given" | |
_cat_help | |
exit 1 | |
fi | |
} | |
_process_mp3() | |
{ | |
# check for match in library playlist | |
# only use n last path components for the search string | |
unset _regex | |
_regex_1_level="/[^/]*" | |
_i="$_num_path_components" | |
while [ "$_i" -gt "0" ]; do | |
_regex="${_regex}${_regex_1_level}" | |
_i="$(expr $_i - 1)" | |
done | |
_regex="${_regex}$" | |
_search_str="$(echo "$_file_noext" | grep -o -E "$_regex")" | |
if [ "$_search_str" ]; then | |
_matches="$(grep "$_search_str" "$_cached_playlist_library")" | |
_num_matches="$(echo "$_matches" | wc -l)" | |
if [ "$_num_matches" -eq "1" ]; then | |
_match="$_matches" | |
elif [ "$_num_matches" -gt "1" ]; then | |
echo "warning: the specified file matches multiple songs in your current playlist" | |
_i_rand="$(shuf -i "1-${_num_matches}" -n 1)" | |
_match="$(echo "$_matches" | sed "${_i_rand}q;d")" | |
fi | |
if [ "$_match" ]; then | |
unset _pl_orig | |
# switch to Library playlist | |
_current_playlist_name="$(audtool current-playlist-name)" | |
if [ "$_current_playlist_name" != "Library" ]; then | |
_pl_orig="$(audtool current-playlist)" | |
_num_pl="$(audtool number-of-playlists)" | |
i=1 | |
while [ "$i" -le "$_num_pl" ]; do | |
audtool set-current-playlist $i | |
_current_playlist_name="$(audtool current-playlist-name)" | |
if [ "$_current_playlist_name" = "Library" ]; then | |
break | |
elif [ "$i" -eq "$_num_pl" ]; then | |
audtool set-current-playlist $_pl_orig | |
fi | |
i="$(expr i + 1)" | |
done | |
fi | |
if [ "$_current_playlist_name" = "Library" ]; then | |
_index="$(echo "$_match" | sed -e 's/^[^0-9]*//g' | cut -d' ' -f1)" | |
echo "match found! index = $_index" | |
# echo "$_match" | |
audtool playqueue-add "$_index" | |
audacious --fwd | |
# audacious --play | |
audtool play-current-playlist | |
# if [ "$_pl_orig" ]; then | |
# audtool set-current-playlist $_pl_orig | |
# fi | |
else | |
audacious "$_file" | |
fi | |
elif [ "$_only_in_playlist" ]; then | |
err 1 "no matching song found in Library playlist for file \"$_file\"" | |
else | |
audacious "$_file" | |
fi | |
else | |
err 1 "no search string left after stripping ($_num_path_components) path components" | |
fi | |
} | |
_process_m3u() | |
{ | |
_m3u_file="$_file" | |
_m3u_name="$_file_noext" | |
_m3u_url="$(cat "$_m3u_file")" | |
# cat "$_cached_playlist_radio" | while read _line; do | |
# _radio_name="$(echo "$_line" | sed -e "s/^.* | //g" -e "s/ | .*$//g")" | |
# _radio_url="$(echo "$_line" | grep -o "http[^|]*" | sed -e "s/ *$//g")" | |
_matches="$(grep " | ${_m3u_url} | " "$_cached_playlist_radio" 2> /dev/null)" | |
_num_matches="$(echo "$_matches" | wc -l)" | |
if [ "$_num_matches" -eq "1" ]; then | |
_match="$_matches" | |
elif [ "$_num_matches" -gt "1" ]; then | |
_first_matched_by_name="$(echo "$_matches" | grep -m 1 " | ${_m3u_name}$" 2> /dev/null)" | |
if [ "$_first_matched_by_name" ]; then | |
_match="$_first_matched_by_name" | |
else | |
echo "warning: the specified file matches multiple songs in your current playlist" | |
_i_rand="$(shuf -i "1-${_num_matches}" -n 1)" | |
_match="$(echo "$_matches" | sed "${_i_rand}q;d")" | |
fi | |
fi | |
if [ "$_match" ]; then | |
# switch to Radio playlist | |
_current_playlist_name="$(audtool current-playlist-name)" | |
unset _pl_orig | |
if [ "$_current_playlist_name" != "Radio" ]; then | |
_pl_orig="$(audtool current-playlist)" | |
_num_pl="$(audtool number-of-playlists)" | |
i=1 | |
while [ "$i" -le "$_num_pl" ]; do | |
audtool set-current-playlist $i | |
_current_playlist_name="$(audtool current-playlist-name)" | |
if [ "$_current_playlist_name" = "Radio" ]; then | |
break | |
elif [ "$i" -eq "$_num_pl" ]; then | |
audtool set-current-playlist $_pl_orig | |
fi | |
i="$(expr $i + 1)" | |
done | |
fi | |
if [ "$_current_playlist_name" = "Radio" ]; then | |
_index="$(echo "$_match" | sed -e 's/^[^0-9]*//g' | cut -d' ' -f1)" | |
echo "match found! index = $_index" | |
# echo "$_match" | |
audtool playqueue-add "$_index" | |
audacious --fwd | |
# audacious --play | |
audtool play-current-playlist | |
# if [ "$_pl_orig" ]; then | |
# audtool set-current-playlist $_pl_orig | |
# fi | |
else | |
audacious "$_file" | |
fi | |
elif [ "$_only_in_playlist" ]; then | |
err 1 "no matching song found in Radio playlist for file \"$_file\"" | |
else | |
audacious "$_file" | |
fi | |
} | |
_main() | |
{ | |
psAudTool="`pgrep -f 'audtool '`" | |
_hung_daemon=0 | |
for pid in $psAudTool; do | |
ptime="$(ps -o etimes= -p "$pid")" | |
if [ "$ptime" -ge "$lastActivationTimeoutSeconds" ]; then | |
_killall=true | |
break | |
fi | |
_hung_daemon="$(expr $i + 1)" | |
done | |
if [ "$_hung_daemon" -gt "1" ]; then | |
_killall=true | |
fi | |
# if a process to activate skippy already exists, with a runtime that is too long | |
# (> 1sec). Then assume skippy-xd is stuck, so we must kill its all of its locked processess | |
if [ "$_killall" ]; then | |
_kill_daemon; | |
fi | |
# if the action requires the skippy-xd daemon, but it is not already running, we should start it | |
if [ ! "$(pgrep -f "$_daemon")" ]; then | |
"$_daemon" "$_daemon_update_interval" & | |
fi | |
if [ "$_file" ]; then | |
if [ ! -e "$_file" ]; then | |
err 1 "the specified file \"$_file\", does not exit" | |
# elif [ ! -f "$_file" ]; then | |
# err "the specified file \"$_file\", is not a regular file" | |
fi | |
_file_ext="$(echo "${_file##*.}")" | |
if [ "$_file_ext" = "m3u" ]; then | |
_process_m3u; | |
else | |
_process_mp3; | |
fi | |
fi | |
} | |
# begin | |
( | |
_check_deps | |
_parse_args "$@"; | |
_main "$@" | |
) | |
Hi @Lupccs sorry for the late reply here. I only just noticed your message today.
The -u
flag is supposed to tell this script to run the _killall
function.
If you run this script as sh -x <scriptfile>
or something like that then it should print out all the commands and enable tracing. To check that the _killall
function is being called. It should then (later on in the script) notice that the daemon is not running. And try to start it again. The first thing the daemon does is supposed to do an update. Then goto sleep. Hope that helps to understand better what is supposed to happen.
Here you can see what happens on my ubuntu 19.10 system. At the end of the output we can see clearly it restarts, and tries to launch aud-fplcacher
with a timeout of 1000 seconds.
$ sh -x aud-jumptofile -u
....
+ [ true ]
+ _killall=true
+ [ ]
+ [ ! true ]
+ _main -u
+ pgrep -f audtool
+ psAudTool=
+ _hung_daemon=0
+ [ 0 -gt 1 ]
+ [ true ]
+ _kill_daemon
+ killall -q aud-fplcacher
+ killall -q audtool
+ [ -e /home/id/.cache/audacious/library.cached_playlist ]
+ rm /home/id/.cache/audacious/library.cached_playlist
+ [ -e /home/id/.cache/audacious/radio.cached_playlist ]
+ rm /home/id/.cache/audacious/radio.cached_playlist
+ pgrep -f aud-fplcacher
+ [ ! ]
+ [ ]
+ aud-fplcacher 1000
Sorry I don't know what was the issue there. But I think you also commented earlier and objected to the hardcoded path at:
https://gist.github.com/dreamcat4/8e6c06a6bcfbd3f0f03c13b5603000db#file-aud-jumptofile-sh-L225
Because it should be something the user can configure. I agree totally. Sorry It's my mistake in writing the program... I should really correct that.
The problem is that is in the embedded script. So the 2nd script is dumb and does not know how to read setting in the main script. However you can edit by hand the line. Same thing here, it's like a 2nd instance declaration:
https://gist.github.com/dreamcat4/8e6c06a6bcfbd3f0f03c13b5603000db#file-aud-jumptofile-sh-L176
Sorry about that. I just don't seem to have time to fix this properly. The right way. Since the program is split into 2 seperate scripts. Perhaps a better solution might be to merge the scripts together. So it can be invoked in 2 different ways. Then we can just declare them all as global variables, these user config settings, at the top of the script.
After some day aud-jumptofile -u still doesn't work but the daemon does the job after a while. oddly, at first it did work properly but after some times now it does find a match in the library but it doesn't play the requested song, it only plays the next song in library (in alphabetical order)!