This is a year later, but it has the same context: I wanted new reviews for the updated code from the original questionthe original question, but this time I wrote the code from scratch. I did this, not because the project is of any value or interest to me any more, as much as I wanted to compare my programming knowledge/style now to mine a year before. So, you will probably notice different algorithms and a different style of programming.
This is a year later, but it has the same context: I wanted new reviews for the updated code from the original question, but this time I wrote the code from scratch. I did this, not because the project is of any value or interest to me any more, as much as I wanted to compare my programming knowledge/style now to mine a year before. So, you will probably notice different algorithms and a different style of programming.
This is a year later, but it has the same context: I wanted new reviews for the updated code from the original question, but this time I wrote the code from scratch. I did this, not because the project is of any value or interest to me any more, as much as I wanted to compare my programming knowledge/style now to mine a year before. So, you will probably notice different algorithms and a different style of programming.
#!/bin/bash
declare -r SettingsFile="$HOME/.config/PlayMusic/settings.conf"
declare -r _LOGFILE="$HOME/.config/PlayMusic/logfile.log"
declare -r Version=0.3.2
source bash_utilbash_lib
log "Program started: $1"
declare -A Settings=(
['Dropbox']=false # Wether to copy downloaded tracks to the dropbox folder
['TimesToPlay']=1 # Number of times to play a track
['Download']=false # Wether to download the track
['Play']=true # Wether to play the track
['All']=false # Wether to act on all tracks found, or just ask to specify track(s)
['Player']='mplayer' # Command to invoke a CLI sound player
['Downloader']='wget -c -O' # Command to invoke a CLI downloader
['Editor']="${EDITOR:-nano}" # Command to invoke a CLI editor
['MusicPlace']="$HOME/Music" # Directory to download music
)
declare Sites=(
'http://ccmixter.org/view/media/samples/mixed'
'http://ccmixter.org/view/media/remix'
'http://mp3.com/top-downloads/genre/jazz/'
'http://mp3.com/top-downloads/'
'http://mp3.com/top-downloads/genre/rock/'
'http://mp3.com/top-downloads/genre/hip hop/'
'http://mp3.com/top-downloads/genre/emo/'
'http://mp3.com/top-downloads/genre/pop/'
) # Array of sites to search for tracks at
declare Extensions=(
'mp3'
'ogg'
) # Array of extensions to look for
# Key: Track title. Value: Track url.
declare -A Tracks
# Displays settings in a user-friendly manner on the screen
info()
{
cat << __EOF_
# General Settings #
Settings File: $SettingsFile
Log File: ${_LOGFILE}
Version: $Version
# Behaviour Settings #
Dropbox Support: ${Settings['Dropbox']}
Music Place: ${Settings['MusicPlace']}
Player: ${Settings['Player']}
Editor: ${Settings['Editor']}
Player: ${Settings['Player']}
# Music Action Settings #
Times to Play: ${Settings['TimesToPlay']}
Act on all tracks: ${Settings['All']}
Action: $( get_music_action )
__EOF_
}
# Saves $Settings and $Sites to $SettingsFile in a valid format
save_settings()
{
log "Creating settings file"
mkdir -p "$( dirname "$SettingsFile" )"
echo "Created on $( date )" > "$SettingsFile" || _FORCED_EXIT=true error "Fatal Error!" $?
for option in "${!Settings[@]}" ; do
echo "$option = ${Settings["$option"]}" >> "$SettingsFile"
done
echo "Sites" >> "$SettingsFile"
for site in "${Sites[@]}" ; do
echo "$site" >> "$SettingsFile"
done
log "Created settings file successfully"
}
# Extracts program name from a command
# @param $1 the cmd.
get_prog_name()
{
cut -d ' ' -f 1 <<< "$1"
}
# Load $Settings and $Sites from the $SettingsFile
load_settings()
{
log "Parsing settings file"
local line_number=0 line
local key value
local correct
local is_site=false site
# Check Settings file
if ! ([ -r "$SettingsFile" ] || [ -f "$SettingsFile" ]) ; then
save_settings
return
fi
while read -r line; do
((line_number++))
if [ -z "$line" ] || [ $line_number = 1 ] ; then
continue
fi
correct=false
# Check line format
if $is_site ; then
Sites["${#Sites[@]}"]="$line"
continue
else
if [ "$line" = "Sites" ] ; then
is_site=true
Sites=()
continue
elif ! egrep "^[a-zA-Z]+\s*=.+$" -q <<< "$line" ; then
error "$SettingsFile:$line_number: Incorrect format of line" EX_CONFIG
fi
fi
# Extract Key,Value pair
key="$( egrep -o '^[a-zA-Z]+' <<< "$line" )"
value="$( sed -E "s/^$key\s*=\s*(.+)$/\1/" <<< "$line" )"
# Check if value is valid
case "$key" in
'Dropbox'|'Download'|'All'|'Play' )
if egrep 'false|true' -q <<< "$value" ; then
correct=true
else
error "Expected true or false"
fi;;
'TimesToPlay' )
if is_num "$value" ; then
correct=true
else
error "Expected a number"
fi;;
'Player'|'Editor'|'Downloader' )
if is_prog_there "$( get_prog_name "$value" )" ; then
correct=true
else
error "Expected an executable program"
fi;;
'MusicPlace' )
if [ -d "$value" ] && [ -w "$value" ] ; then
correct=true;
else
error "'$value' is not a writable directory"
fi;;
* ) error "$SettingsFile:$line_number: Invalid option: $key\nValid Options are:\n" \
"\tDropbox, Download, All, Play, TimesToPlay, Player, Editor" EX_CONFIG;;
esac
if ! $correct ; then
error "$SettingsFile:$line_number: Invalid value: '$value' to option: '$key'" EX_CONFIG
fi
Settings["$key"]="$value"
done < "$SettingsFile"
log "Parsed settings file successfully"
}
# Displays program usage in a user-friendly manner to screen
usage()
{
cat << _USAGE_
Usage: ./PlayMusic
-v|--version Output version then exit.
-h|--help View this help then exit.
-x|--dropbox Allow copying downloaded files to $HOME/Dropbox.
-t|--playtimes [num] Times to play a track.
-d|--download To download a track without asking.
-D|--no-download To not download a track without asking.
-p|--play To play a track without asking.
-P|--no-play To not play a track without asking.
-k|--ask To force ask what to do with a track.
-a|--all To act on all tracks found.
-y|--player [cmd] The command to run the music player.
-e|--edtor [cmd] The command to run the editor.
-l|--downloader The command to run the downloader.
-m|--music-place [dir] To specify a music directory other than the one found at the settings file.
-r|--recreate-settings To recreate the settings file to default then exit.
-E|--edit-settings To edit the settings file then exit.
-s|--save To save the given settings (runs after analyzing all options).
-i|--info To display given settings so far then exit.
_USAGE_
}
# Outputs the function name of what to do with a track
get_music_action()
{
if ${Settings['Download']} && ! ${Settings['Play']} ; then
echo "download"
elif ! ${Settings['Download']} && ${Settings['Play']} ; then
echo "play"
elif ${Settings['Download']} && ${Settings['Play']} ; then
echo "download_then_play"
# else
# echo "ask"
fi
}
# Parses program arguments
# @param $1 program arguments
parse_args()
{
log "Parsing Arguments"
local save=false
local args=`getopt -o vhxt:dpDPy:e:amX::islk --long version,help,dropbox,playtimes:,download,play,no-download,no-play,player:,editor:,all,music-place:,extensions::,info,save,downloader,ask -n 'PlayMusic' -- "$@"` || error "Internal Error!" EX_SOFTWARE
eval set -- "$args"
while true ; do
case "$1" in
-v|--version ) echo "$Version"; _exit;;
-h|--help ) usage; _exit;;
-i|--info ) info; _exit;;
-x|--dropbox ) Settings['Dropbox']=true;;
-t|--playtimes )
if is_num "$2" ; then
Settings['TimesToPlay']="$2"
else
if [ -n "$2" ] ; then
error "'$2' is not a number" EX_CONFIG
else
error "Please provide a number for the '$1' option" EX_CONFIG
fi
fi; shift;;
-d|--download ) Settings['Download']=true;;
-p|--play ) Settings['Play']=true;;
-D|--no-download ) Settings['Download']=false;;
-P|--no-play ) Settings['Play']=false;;
-y|--player ) require "$( get_prog_name "$2" )" 2; Settings['Player']="$2"; shift;;
-e|--editor ) require "$( get_prog_name "$2" )" 2; Settings['Editor']="$2"; shift;;
-a|--all ) Settings['All']=true;;
-A|--selective ) Settings['All']=false;;
-m|--music-place )
if ! ([ -d "$2" ] && [ -w "$2" ]) ; then
error "'$2' is not a writable directory to store music in" EX_CONFIG
fi;;
-X|--extensions )
if [ -n "$2" ] ; then
Extensions=(${2//,/ })
else
echo "Extensions: ${Extensions[@]// /,}"
fi;;
-s|--save ) save=true;;
-l|--downloader ) require "$( get_prog_name "$2" )" 2; Settings['Downloader']="$2"; shift;;
-k|--ask ) Settings['Play']=false; Settings['Download']=false;;
-- ) break;;
* ) error "Unknown argument: $1" EX_CONFIG;;
esac
shift
done
if ${Settings['All']} ; then
local act=$( get_music_action )
[ -z act ] && act="ask what to do with"
is_yes -p "Are you sure you want to $act all tracks [y/n] ? " || Settings['All']=false
fi
if $save ; then
log "Saving settings"
save_settings
exit
fi
log "Parsed Arguments successfully"
}
# Fills $Tracks by looking through $Sites for tracks ending in $Extensions. This is the core backend functionality.
find_tracks()
{
log "Looking for tracks"
local exts="${Extensions[@]// /|}"
local num
for site in "${Sites[@]}" ; do
log "Checking site: '$site'"
num=0
[ "$1" = '-v' ] && echo "Parsing $site"
for track in $( curl -Ls "$site" | egrep -o "\bhttp://.*\.("$exts")\b" ) ; do
name="$( url_decode "$( basename "$track" )" auto )"
Tracks["${name//+/ }"]="$track"
((num++))
done
log "Found $num track(s) at the site"
done
[ ${#Tracks[@]} = 0 ] && error "Couldn't find any tracks!" 0
}
# Edits $SettingsFile by ${Settings['Editor']}
edit_settings()
{
log 'Edit settings requested'
${Settings['Editor']} "$SettingsFile"
}
# Handles music action
# @param $1 action
# @param $2 track url
# @param $3 track name (with extension)
handle_action()
{
([ -n "$1" ] && [ -n "$2" ] && [ -n "$3" ]) || return
[ "$1" = 'download' ] || local name="$( sed -E 's/\.(\w+)$//' <<< "$3" )"
$1 "$2" "$([ "$1" = 'play' ] && echo "$name" || echo "$3")" "$([ "$1" = 'download_then_play' ] && echo "$name")"
}
# Downloads a track to ${Settings['MusicPlace']} by ${Settings['Downloader']}
# If $3 is -v, Outputs the track location on disk.
# @param $1 the track url
# @param $2 the track name (with extension)
# @param $3 -v. optional.
download()
{
local download_to="${Settings['MusicPlace']}/$2"
log "Action: Download [$1] to [$download_to]"
wget -c -O "$download_to" "$1"
log "wget returned $?"
if $Settings['Dropbox'] ; then
cp "$download_to" "$HOME/Dropbox/Music"
log "Copying file to dropbox"
fi
[ "$3" = '-v' ] && echo "$download_to"
}
# Plays a track by ${Settings['Player']}
# @param $1 the track location
# @param $2 the track name (without extension). optional.
play()
{
log "Action: Play [$1]"
[ -n "$2" ] && notify-send "PlayMusic: Playing $2"
${Settings['Player']} "$1"
}
# Downloads then plays a track
# @param $1 the track url
# @param $2 the track name (with extension).
# @param $3 the track name (without extension). optional.
download_then_play()
{
log "Action: Download then Play [$1]"
play $( download "$1" "$2" -v ) "$3"
}
# Asks the user what to do with a track
# @param $1 the other option, outputs nothing when chosen.
ask()
{
local -A _MENU_CHOICES=(['Download']='download' ['Play']='play' ['Download then Play']='download_then_play' ["$1"]='')
menu
}
# Main entry point
main()
{
local site_number=0
local com="$( get_music_action )"
echo "Looking for Tracks in ${#Sites[@]} Site$([[ ${#Sites[@]} -gt 1 ]] && echo "s") .."
find_tracks -v
if ${Settings['All']} ; then
[ -n "$com" ] || com="$( ask 'Exit' )"
[ -n "$com" ] || _exit
for track in "${Tracks[@]}" ; do
handle_action "$com" "${Tracks["$track"]}" "$track"
done
else
echo "Choose Track .."
select track in "${!Tracks[@]}" 'Quit' ; do
[ -n "$track" ] || continue
[ "$track" = 'Quit' ] && _exit
[ -n "$com" ] || com="$( ask 'Return back' )"
[ -n "$com" ] || continue
handle_action "$com" "${Tracks["$track"]}" "$track"
done
fi
}
# Handles signales. Should not be used directly.
forced_exit()
{
_FORCED_EXIT=true error "Signal [$1] forced an exit." "$((128+$1))"
}
# Initializes the environment for processing
init_env()
{
trap 'forced_exit 2' SIGINT
trap 'forced_exit 3' SIGQUIT
trap 'forced_exit 4' SIGABRT
trap 'forced_exit 15' SIGTERM
for i in "$@" ; do
if [ "$i" = "-r" ] || [ "$i" = '--recreate-settings' ] ; then
save_settings
_exit
fi
if [ "$i" = "-E" ] || [ "$i" = '--edit-settings' ] ; then
edit_settings
_exit
fi
done
mkdir -p "${Settings['MusicPlace']}"
load_settings
parse_args "$@"
if [ ${#Sites[@]} = 0 ] ; then
error "No Sites were found!" 0
fi
}
ARGS="$@"
init_env "$ARGS"
require "$( get_prog_name "${Settings['Player']}" )" "$( get_prog_name "${Settings['Editor']}" )" "$( get_prog_name "${Settings['Downloader']}" )"
main "$ARGS"
#!/bin/bash
declare -r SettingsFile="$HOME/.config/PlayMusic/settings.conf"
declare -r _LOGFILE="$HOME/.config/PlayMusic/logfile.log"
declare -r Version=0.3.2
source bash_util
log "Program started: $1"
declare -A Settings=(
['Dropbox']=false # Wether to copy downloaded tracks to the dropbox folder
['TimesToPlay']=1 # Number of times to play a track
['Download']=false # Wether to download the track
['Play']=true # Wether to play the track
['All']=false # Wether to act on all tracks found, or just ask to specify track(s)
['Player']='mplayer' # Command to invoke a CLI sound player
['Downloader']='wget -c -O' # Command to invoke a CLI downloader
['Editor']="${EDITOR:-nano}" # Command to invoke a CLI editor
['MusicPlace']="$HOME/Music" # Directory to download music
)
declare Sites=(
'http://ccmixter.org/view/media/samples/mixed'
'http://ccmixter.org/view/media/remix'
'http://mp3.com/top-downloads/genre/jazz/'
'http://mp3.com/top-downloads/'
'http://mp3.com/top-downloads/genre/rock/'
'http://mp3.com/top-downloads/genre/hip hop/'
'http://mp3.com/top-downloads/genre/emo/'
'http://mp3.com/top-downloads/genre/pop/'
) # Array of sites to search for tracks at
declare Extensions=(
'mp3'
'ogg'
) # Array of extensions to look for
# Key: Track title. Value: Track url.
declare -A Tracks
# Displays settings in a user-friendly manner on the screen
info()
{
cat << __EOF_
# General Settings #
Settings File: $SettingsFile
Log File: ${_LOGFILE}
Version: $Version
# Behaviour Settings #
Dropbox Support: ${Settings['Dropbox']}
Music Place: ${Settings['MusicPlace']}
Player: ${Settings['Player']}
Editor: ${Settings['Editor']}
Player: ${Settings['Player']}
# Music Action Settings #
Times to Play: ${Settings['TimesToPlay']}
Act on all tracks: ${Settings['All']}
Action: $( get_music_action )
__EOF_
}
# Saves $Settings and $Sites to $SettingsFile in a valid format
save_settings()
{
log "Creating settings file"
mkdir -p "$( dirname "$SettingsFile" )"
echo "Created on $( date )" > "$SettingsFile" || _FORCED_EXIT=true error "Fatal Error!" $?
for option in "${!Settings[@]}" ; do
echo "$option = ${Settings["$option"]}" >> "$SettingsFile"
done
echo "Sites" >> "$SettingsFile"
for site in "${Sites[@]}" ; do
echo "$site" >> "$SettingsFile"
done
log "Created settings file successfully"
}
# Extracts program name from a command
# @param $1 the cmd.
get_prog_name()
{
cut -d ' ' -f 1 <<< "$1"
}
# Load $Settings and $Sites from the $SettingsFile
load_settings()
{
log "Parsing settings file"
local line_number=0 line
local key value
local correct
local is_site=false site
# Check Settings file
if ! ([ -r "$SettingsFile" ] || [ -f "$SettingsFile" ]) ; then
save_settings
return
fi
while read -r line; do
((line_number++))
if [ -z "$line" ] || [ $line_number = 1 ] ; then
continue
fi
correct=false
# Check line format
if $is_site ; then
Sites["${#Sites[@]}"]="$line"
continue
else
if [ "$line" = "Sites" ] ; then
is_site=true
Sites=()
continue
elif ! egrep "^[a-zA-Z]+\s*=.+$" -q <<< "$line" ; then
error "$SettingsFile:$line_number: Incorrect format of line" EX_CONFIG
fi
fi
# Extract Key,Value pair
key="$( egrep -o '^[a-zA-Z]+' <<< "$line" )"
value="$( sed -E "s/^$key\s*=\s*(.+)$/\1/" <<< "$line" )"
# Check if value is valid
case "$key" in
'Dropbox'|'Download'|'All'|'Play' )
if egrep 'false|true' -q <<< "$value" ; then
correct=true
else
error "Expected true or false"
fi;;
'TimesToPlay' )
if is_num "$value" ; then
correct=true
else
error "Expected a number"
fi;;
'Player'|'Editor'|'Downloader' )
if is_prog_there "$( get_prog_name "$value" )" ; then
correct=true
else
error "Expected an executable program"
fi;;
'MusicPlace' )
if [ -d "$value" ] && [ -w "$value" ] ; then
correct=true;
else
error "'$value' is not a writable directory"
fi;;
* ) error "$SettingsFile:$line_number: Invalid option: $key\nValid Options are:\n" \
"\tDropbox, Download, All, Play, TimesToPlay, Player, Editor" EX_CONFIG;;
esac
if ! $correct ; then
error "$SettingsFile:$line_number: Invalid value: '$value' to option: '$key'" EX_CONFIG
fi
Settings["$key"]="$value"
done < "$SettingsFile"
log "Parsed settings file successfully"
}
# Displays program usage in a user-friendly manner to screen
usage()
{
cat << _USAGE_
Usage: ./PlayMusic
-v|--version Output version then exit.
-h|--help View this help then exit.
-x|--dropbox Allow copying downloaded files to $HOME/Dropbox.
-t|--playtimes [num] Times to play a track.
-d|--download To download a track without asking.
-D|--no-download To not download a track without asking.
-p|--play To play a track without asking.
-P|--no-play To not play a track without asking.
-k|--ask To force ask what to do with a track.
-a|--all To act on all tracks found.
-y|--player [cmd] The command to run the music player.
-e|--edtor [cmd] The command to run the editor.
-l|--downloader The command to run the downloader.
-m|--music-place [dir] To specify a music directory other than the one found at the settings file.
-r|--recreate-settings To recreate the settings file to default then exit.
-E|--edit-settings To edit the settings file then exit.
-s|--save To save the given settings (runs after analyzing all options).
-i|--info To display given settings so far then exit.
_USAGE_
}
# Outputs the function name of what to do with a track
get_music_action()
{
if ${Settings['Download']} && ! ${Settings['Play']} ; then
echo "download"
elif ! ${Settings['Download']} && ${Settings['Play']} ; then
echo "play"
elif ${Settings['Download']} && ${Settings['Play']} ; then
echo "download_then_play"
# else
# echo "ask"
fi
}
# Parses program arguments
# @param $1 program arguments
parse_args()
{
log "Parsing Arguments"
local save=false
local args=`getopt -o vhxt:dpDPy:e:amX::islk --long version,help,dropbox,playtimes:,download,play,no-download,no-play,player:,editor:,all,music-place:,extensions::,info,save,downloader,ask -n 'PlayMusic' -- "$@"` || error "Internal Error!" EX_SOFTWARE
eval set -- "$args"
while true ; do
case "$1" in
-v|--version ) echo "$Version"; _exit;;
-h|--help ) usage; _exit;;
-i|--info ) info; _exit;;
-x|--dropbox ) Settings['Dropbox']=true;;
-t|--playtimes )
if is_num "$2" ; then
Settings['TimesToPlay']="$2"
else
if [ -n "$2" ] ; then
error "'$2' is not a number" EX_CONFIG
else
error "Please provide a number for the '$1' option" EX_CONFIG
fi
fi; shift;;
-d|--download ) Settings['Download']=true;;
-p|--play ) Settings['Play']=true;;
-D|--no-download ) Settings['Download']=false;;
-P|--no-play ) Settings['Play']=false;;
-y|--player ) require "$( get_prog_name "$2" )" 2; Settings['Player']="$2"; shift;;
-e|--editor ) require "$( get_prog_name "$2" )" 2; Settings['Editor']="$2"; shift;;
-a|--all ) Settings['All']=true;;
-A|--selective ) Settings['All']=false;;
-m|--music-place )
if ! ([ -d "$2" ] && [ -w "$2" ]) ; then
error "'$2' is not a writable directory to store music in" EX_CONFIG
fi;;
-X|--extensions )
if [ -n "$2" ] ; then
Extensions=(${2//,/ })
else
echo "Extensions: ${Extensions[@]// /,}"
fi;;
-s|--save ) save=true;;
-l|--downloader ) require "$( get_prog_name "$2" )" 2; Settings['Downloader']="$2"; shift;;
-k|--ask ) Settings['Play']=false; Settings['Download']=false;;
-- ) break;;
* ) error "Unknown argument: $1" EX_CONFIG;;
esac
shift
done
if ${Settings['All']} ; then
local act=$( get_music_action )
[ -z act ] && act="ask what to do with"
is_yes -p "Are you sure you want to $act all tracks [y/n] ? " || Settings['All']=false
fi
if $save ; then
log "Saving settings"
save_settings
exit
fi
log "Parsed Arguments successfully"
}
# Fills $Tracks by looking through $Sites for tracks ending in $Extensions. This is the core backend functionality.
find_tracks()
{
log "Looking for tracks"
local exts="${Extensions[@]// /|}"
local num
for site in "${Sites[@]}" ; do
log "Checking site: '$site'"
num=0
[ "$1" = '-v' ] && echo "Parsing $site"
for track in $( curl -Ls "$site" | egrep -o "\bhttp://.*\.("$exts")\b" ) ; do
name="$( url_decode "$( basename "$track" )" auto )"
Tracks["${name//+/ }"]="$track"
((num++))
done
log "Found $num track(s) at the site"
done
[ ${#Tracks[@]} = 0 ] && error "Couldn't find any tracks!" 0
}
# Edits $SettingsFile by ${Settings['Editor']}
edit_settings()
{
log 'Edit settings requested'
${Settings['Editor']} "$SettingsFile"
}
# Handles music action
# @param $1 action
# @param $2 track url
# @param $3 track name (with extension)
handle_action()
{
([ -n "$1" ] && [ -n "$2" ] && [ -n "$3" ]) || return
[ "$1" = 'download' ] || local name="$( sed -E 's/\.(\w+)$//' <<< "$3" )"
$1 "$2" "$([ "$1" = 'play' ] && echo "$name" || echo "$3")" "$([ "$1" = 'download_then_play' ] && echo "$name")"
}
# Downloads a track to ${Settings['MusicPlace']} by ${Settings['Downloader']}
# If $3 is -v, Outputs the track location on disk.
# @param $1 the track url
# @param $2 the track name (with extension)
# @param $3 -v. optional.
download()
{
local download_to="${Settings['MusicPlace']}/$2"
log "Action: Download [$1] to [$download_to]"
wget -c -O "$download_to" "$1"
log "wget returned $?"
if $Settings['Dropbox'] ; then
cp "$download_to" "$HOME/Dropbox/Music"
log "Copying file to dropbox"
fi
[ "$3" = '-v' ] && echo "$download_to"
}
# Plays a track by ${Settings['Player']}
# @param $1 the track location
# @param $2 the track name (without extension). optional.
play()
{
log "Action: Play [$1]"
[ -n "$2" ] && notify-send "PlayMusic: Playing $2"
${Settings['Player']} "$1"
}
# Downloads then plays a track
# @param $1 the track url
# @param $2 the track name (with extension).
# @param $3 the track name (without extension). optional.
download_then_play()
{
log "Action: Download then Play [$1]"
play $( download "$1" "$2" -v ) "$3"
}
# Asks the user what to do with a track
# @param $1 the other option, outputs nothing when chosen.
ask()
{
local -A _MENU_CHOICES=(['Download']='download' ['Play']='play' ['Download then Play']='download_then_play' ["$1"]='')
menu
}
# Main entry point
main()
{
local site_number=0
local com="$( get_music_action )"
echo "Looking for Tracks in ${#Sites[@]} Site$([[ ${#Sites[@]} -gt 1 ]] && echo "s") .."
find_tracks -v
if ${Settings['All']} ; then
[ -n "$com" ] || com="$( ask 'Exit' )"
[ -n "$com" ] || _exit
for track in "${Tracks[@]}" ; do
handle_action "$com" "${Tracks["$track"]}" "$track"
done
else
echo "Choose Track .."
select track in "${!Tracks[@]}" 'Quit' ; do
[ -n "$track" ] || continue
[ "$track" = 'Quit' ] && _exit
[ -n "$com" ] || com="$( ask 'Return back' )"
[ -n "$com" ] || continue
handle_action "$com" "${Tracks["$track"]}" "$track"
done
fi
}
# Handles signales. Should not be used directly.
forced_exit()
{
_FORCED_EXIT=true error "Signal [$1] forced an exit." "$((128+$1))"
}
# Initializes the environment for processing
init_env()
{
trap 'forced_exit 2' SIGINT
trap 'forced_exit 3' SIGQUIT
trap 'forced_exit 4' SIGABRT
trap 'forced_exit 15' SIGTERM
for i in "$@" ; do
if [ "$i" = "-r" ] || [ "$i" = '--recreate-settings' ] ; then
save_settings
_exit
fi
if [ "$i" = "-E" ] || [ "$i" = '--edit-settings' ] ; then
edit_settings
_exit
fi
done
mkdir -p "${Settings['MusicPlace']}"
load_settings
parse_args "$@"
if [ ${#Sites[@]} = 0 ] ; then
error "No Sites were found!" 0
fi
}
ARGS="$@"
init_env "$ARGS"
require "$( get_prog_name "${Settings['Player']}" )" "$( get_prog_name "${Settings['Editor']}" )" "$( get_prog_name "${Settings['Downloader']}" )"
main "$ARGS"
#!/bin/bash
declare -r SettingsFile="$HOME/.config/PlayMusic/settings.conf"
declare -r _LOGFILE="$HOME/.config/PlayMusic/logfile.log"
declare -r Version=0.3.2
source bash_lib
log "Program started: $1"
declare -A Settings=(
['Dropbox']=false # Wether to copy downloaded tracks to the dropbox folder
['TimesToPlay']=1 # Number of times to play a track
['Download']=false # Wether to download the track
['Play']=true # Wether to play the track
['All']=false # Wether to act on all tracks found, or just ask to specify track(s)
['Player']='mplayer' # Command to invoke a CLI sound player
['Downloader']='wget -c -O' # Command to invoke a CLI downloader
['Editor']="${EDITOR:-nano}" # Command to invoke a CLI editor
['MusicPlace']="$HOME/Music" # Directory to download music
)
declare Sites=(
'http://ccmixter.org/view/media/samples/mixed'
'http://ccmixter.org/view/media/remix'
'http://mp3.com/top-downloads/genre/jazz/'
'http://mp3.com/top-downloads/'
'http://mp3.com/top-downloads/genre/rock/'
'http://mp3.com/top-downloads/genre/hip hop/'
'http://mp3.com/top-downloads/genre/emo/'
'http://mp3.com/top-downloads/genre/pop/'
) # Array of sites to search for tracks at
declare Extensions=(
'mp3'
'ogg'
) # Array of extensions to look for
# Key: Track title. Value: Track url.
declare -A Tracks
# Displays settings in a user-friendly manner on the screen
info()
{
cat << __EOF_
# General Settings #
Settings File: $SettingsFile
Log File: ${_LOGFILE}
Version: $Version
# Behaviour Settings #
Dropbox Support: ${Settings['Dropbox']}
Music Place: ${Settings['MusicPlace']}
Player: ${Settings['Player']}
Editor: ${Settings['Editor']}
Player: ${Settings['Player']}
# Music Action Settings #
Times to Play: ${Settings['TimesToPlay']}
Act on all tracks: ${Settings['All']}
Action: $( get_music_action )
__EOF_
}
# Saves $Settings and $Sites to $SettingsFile in a valid format
save_settings()
{
log "Creating settings file"
mkdir -p "$( dirname "$SettingsFile" )"
echo "Created on $( date )" > "$SettingsFile" || _FORCED_EXIT=true error "Fatal Error!" $?
for option in "${!Settings[@]}" ; do
echo "$option = ${Settings["$option"]}" >> "$SettingsFile"
done
echo "Sites" >> "$SettingsFile"
for site in "${Sites[@]}" ; do
echo "$site" >> "$SettingsFile"
done
log "Created settings file successfully"
}
# Extracts program name from a command
# @param $1 the cmd.
get_prog_name()
{
cut -d ' ' -f 1 <<< "$1"
}
# Load $Settings and $Sites from the $SettingsFile
load_settings()
{
log "Parsing settings file"
local line_number=0 line
local key value
local correct
local is_site=false site
# Check Settings file
if ! ([ -r "$SettingsFile" ] || [ -f "$SettingsFile" ]) ; then
save_settings
return
fi
while read -r line; do
((line_number++))
if [ -z "$line" ] || [ $line_number = 1 ] ; then
continue
fi
correct=false
# Check line format
if $is_site ; then
Sites["${#Sites[@]}"]="$line"
continue
else
if [ "$line" = "Sites" ] ; then
is_site=true
Sites=()
continue
elif ! egrep "^[a-zA-Z]+\s*=.+$" -q <<< "$line" ; then
error "$SettingsFile:$line_number: Incorrect format of line" EX_CONFIG
fi
fi
# Extract Key,Value pair
key="$( egrep -o '^[a-zA-Z]+' <<< "$line" )"
value="$( sed -E "s/^$key\s*=\s*(.+)$/\1/" <<< "$line" )"
# Check if value is valid
case "$key" in
'Dropbox'|'Download'|'All'|'Play' )
if egrep 'false|true' -q <<< "$value" ; then
correct=true
else
error "Expected true or false"
fi;;
'TimesToPlay' )
if is_num "$value" ; then
correct=true
else
error "Expected a number"
fi;;
'Player'|'Editor'|'Downloader' )
if is_prog_there "$( get_prog_name "$value" )" ; then
correct=true
else
error "Expected an executable program"
fi;;
'MusicPlace' )
if [ -d "$value" ] && [ -w "$value" ] ; then
correct=true;
else
error "'$value' is not a writable directory"
fi;;
* ) error "$SettingsFile:$line_number: Invalid option: $key\nValid Options are:\n" \
"\tDropbox, Download, All, Play, TimesToPlay, Player, Editor" EX_CONFIG;;
esac
if ! $correct ; then
error "$SettingsFile:$line_number: Invalid value: '$value' to option: '$key'" EX_CONFIG
fi
Settings["$key"]="$value"
done < "$SettingsFile"
log "Parsed settings file successfully"
}
# Displays program usage in a user-friendly manner to screen
usage()
{
cat << _USAGE_
Usage: ./PlayMusic
-v|--version Output version then exit.
-h|--help View this help then exit.
-x|--dropbox Allow copying downloaded files to $HOME/Dropbox.
-t|--playtimes [num] Times to play a track.
-d|--download To download a track without asking.
-D|--no-download To not download a track without asking.
-p|--play To play a track without asking.
-P|--no-play To not play a track without asking.
-k|--ask To force ask what to do with a track.
-a|--all To act on all tracks found.
-y|--player [cmd] The command to run the music player.
-e|--edtor [cmd] The command to run the editor.
-l|--downloader The command to run the downloader.
-m|--music-place [dir] To specify a music directory other than the one found at the settings file.
-r|--recreate-settings To recreate the settings file to default then exit.
-E|--edit-settings To edit the settings file then exit.
-s|--save To save the given settings (runs after analyzing all options).
-i|--info To display given settings so far then exit.
_USAGE_
}
# Outputs the function name of what to do with a track
get_music_action()
{
if ${Settings['Download']} && ! ${Settings['Play']} ; then
echo "download"
elif ! ${Settings['Download']} && ${Settings['Play']} ; then
echo "play"
elif ${Settings['Download']} && ${Settings['Play']} ; then
echo "download_then_play"
# else
# echo "ask"
fi
}
# Parses program arguments
# @param $1 program arguments
parse_args()
{
log "Parsing Arguments"
local save=false
local args=`getopt -o vhxt:dpDPy:e:amX::islk --long version,help,dropbox,playtimes:,download,play,no-download,no-play,player:,editor:,all,music-place:,extensions::,info,save,downloader,ask -n 'PlayMusic' -- "$@"` || error "Internal Error!" EX_SOFTWARE
eval set -- "$args"
while true ; do
case "$1" in
-v|--version ) echo "$Version"; _exit;;
-h|--help ) usage; _exit;;
-i|--info ) info; _exit;;
-x|--dropbox ) Settings['Dropbox']=true;;
-t|--playtimes )
if is_num "$2" ; then
Settings['TimesToPlay']="$2"
else
if [ -n "$2" ] ; then
error "'$2' is not a number" EX_CONFIG
else
error "Please provide a number for the '$1' option" EX_CONFIG
fi
fi; shift;;
-d|--download ) Settings['Download']=true;;
-p|--play ) Settings['Play']=true;;
-D|--no-download ) Settings['Download']=false;;
-P|--no-play ) Settings['Play']=false;;
-y|--player ) require "$( get_prog_name "$2" )" 2; Settings['Player']="$2"; shift;;
-e|--editor ) require "$( get_prog_name "$2" )" 2; Settings['Editor']="$2"; shift;;
-a|--all ) Settings['All']=true;;
-A|--selective ) Settings['All']=false;;
-m|--music-place )
if ! ([ -d "$2" ] && [ -w "$2" ]) ; then
error "'$2' is not a writable directory to store music in" EX_CONFIG
fi;;
-X|--extensions )
if [ -n "$2" ] ; then
Extensions=(${2//,/ })
else
echo "Extensions: ${Extensions[@]// /,}"
fi;;
-s|--save ) save=true;;
-l|--downloader ) require "$( get_prog_name "$2" )" 2; Settings['Downloader']="$2"; shift;;
-k|--ask ) Settings['Play']=false; Settings['Download']=false;;
-- ) break;;
* ) error "Unknown argument: $1" EX_CONFIG;;
esac
shift
done
if ${Settings['All']} ; then
local act=$( get_music_action )
[ -z act ] && act="ask what to do with"
is_yes -p "Are you sure you want to $act all tracks [y/n] ? " || Settings['All']=false
fi
if $save ; then
log "Saving settings"
save_settings
exit
fi
log "Parsed Arguments successfully"
}
# Fills $Tracks by looking through $Sites for tracks ending in $Extensions. This is the core backend functionality.
find_tracks()
{
log "Looking for tracks"
local exts="${Extensions[@]// /|}"
local num
for site in "${Sites[@]}" ; do
log "Checking site: '$site'"
num=0
[ "$1" = '-v' ] && echo "Parsing $site"
for track in $( curl -Ls "$site" | egrep -o "\bhttp://.*\.("$exts")\b" ) ; do
name="$( url_decode "$( basename "$track" )" auto )"
Tracks["${name//+/ }"]="$track"
((num++))
done
log "Found $num track(s) at the site"
done
[ ${#Tracks[@]} = 0 ] && error "Couldn't find any tracks!" 0
}
# Edits $SettingsFile by ${Settings['Editor']}
edit_settings()
{
log 'Edit settings requested'
${Settings['Editor']} "$SettingsFile"
}
# Handles music action
# @param $1 action
# @param $2 track url
# @param $3 track name (with extension)
handle_action()
{
([ -n "$1" ] && [ -n "$2" ] && [ -n "$3" ]) || return
[ "$1" = 'download' ] || local name="$( sed -E 's/\.(\w+)$//' <<< "$3" )"
$1 "$2" "$([ "$1" = 'play' ] && echo "$name" || echo "$3")" "$([ "$1" = 'download_then_play' ] && echo "$name")"
}
# Downloads a track to ${Settings['MusicPlace']} by ${Settings['Downloader']}
# If $3 is -v, Outputs the track location on disk.
# @param $1 the track url
# @param $2 the track name (with extension)
# @param $3 -v. optional.
download()
{
local download_to="${Settings['MusicPlace']}/$2"
log "Action: Download [$1] to [$download_to]"
wget -c -O "$download_to" "$1"
log "wget returned $?"
if $Settings['Dropbox'] ; then
cp "$download_to" "$HOME/Dropbox/Music"
log "Copying file to dropbox"
fi
[ "$3" = '-v' ] && echo "$download_to"
}
# Plays a track by ${Settings['Player']}
# @param $1 the track location
# @param $2 the track name (without extension). optional.
play()
{
log "Action: Play [$1]"
[ -n "$2" ] && notify-send "PlayMusic: Playing $2"
${Settings['Player']} "$1"
}
# Downloads then plays a track
# @param $1 the track url
# @param $2 the track name (with extension).
# @param $3 the track name (without extension). optional.
download_then_play()
{
log "Action: Download then Play [$1]"
play $( download "$1" "$2" -v ) "$3"
}
# Asks the user what to do with a track
# @param $1 the other option, outputs nothing when chosen.
ask()
{
local -A _MENU_CHOICES=(['Download']='download' ['Play']='play' ['Download then Play']='download_then_play' ["$1"]='')
menu
}
# Main entry point
main()
{
local site_number=0
local com="$( get_music_action )"
echo "Looking for Tracks in ${#Sites[@]} Site$([[ ${#Sites[@]} -gt 1 ]] && echo "s") .."
find_tracks -v
if ${Settings['All']} ; then
[ -n "$com" ] || com="$( ask 'Exit' )"
[ -n "$com" ] || _exit
for track in "${Tracks[@]}" ; do
handle_action "$com" "${Tracks["$track"]}" "$track"
done
else
echo "Choose Track .."
select track in "${!Tracks[@]}" 'Quit' ; do
[ -n "$track" ] || continue
[ "$track" = 'Quit' ] && _exit
[ -n "$com" ] || com="$( ask 'Return back' )"
[ -n "$com" ] || continue
handle_action "$com" "${Tracks["$track"]}" "$track"
done
fi
}
# Handles signales. Should not be used directly.
forced_exit()
{
_FORCED_EXIT=true error "Signal [$1] forced an exit." "$((128+$1))"
}
# Initializes the environment for processing
init_env()
{
trap 'forced_exit 2' SIGINT
trap 'forced_exit 3' SIGQUIT
trap 'forced_exit 4' SIGABRT
trap 'forced_exit 15' SIGTERM
for i in "$@" ; do
if [ "$i" = "-r" ] || [ "$i" = '--recreate-settings' ] ; then
save_settings
_exit
fi
if [ "$i" = "-E" ] || [ "$i" = '--edit-settings' ] ; then
edit_settings
_exit
fi
done
mkdir -p "${Settings['MusicPlace']}"
load_settings
parse_args "$@"
if [ ${#Sites[@]} = 0 ] ; then
error "No Sites were found!" 0
fi
}
ARGS="$@"
init_env "$ARGS"
require "$( get_prog_name "${Settings['Player']}" )" "$( get_prog_name "${Settings['Editor']}" )" "$( get_prog_name "${Settings['Downloader']}" )"
main "$ARGS"
Simple Bash Music Player - follow-up
The program uses a bash utility library I've created for common tasks, called bash_lib
. Here it is:
Thanks in advance.
Simple Bash Music Player
The program uses a bash utility library I've created for common tasks, called bash_lib
. Here it is:
Thanks in advance.
Simple Bash Music Player - follow-up
The program uses a bash utility library I've created for common tasks, called bash_lib
: