Skip to content

Instantly share code, notes, and snippets.

@frabjous
Created May 31, 2022 12:28
Show Gist options
  • Save frabjous/264493985b0968641d99db17512f7fd8 to your computer and use it in GitHub Desktop.
Save frabjous/264493985b0968641d99db17512f7fd8 to your computer and use it in GitHub Desktop.
Script for browsing music for mpd with mpc and kitty's icat kitten
#!/bin/bash
# Define colors
txtblk='\e[0;30m' # Black - Regular
txtred='\e[0;31m' # Red
txtgrn='\e[0;32m' # Green
txtylw='\e[0;33m' # Yellow
txtblu='\e[0;34m' # Blue
txtpur='\e[0;35m' # Purple
txtcyn='\e[0;36m' # Cyan
txtwht='\e[0;37m' # White
bldblk='\e[1;30m' # Black - Bold
bldred='\e[1;31m' # Red
bldgrn='\e[1;32m' # Green
bldylw='\e[1;33m' # Yellow
bldblu='\e[1;34m' # Blue
bldpur='\e[1;35m' # Purple
bldcyn='\e[1;36m' # Cyan
bldwht='\e[1;37m' # White
unkblk='\e[4;30m' # Black - Underline
undred='\e[4;31m' # Red
undgrn='\e[4;32m' # Green
undylw='\e[4;33m' # Yellow
undblu='\e[4;34m' # Blue
undpur='\e[4;35m' # Purple
undcyn='\e[4;36m' # Cyan
undwht='\e[4;37m' # White
bakblk='\e[40m' # Black - Background
bakred='\e[41m' # Red
bakgrn='\e[42m' # Green
bakylw='\e[43m' # Yellow
bakblu='\e[44m' # Blue
bakpur='\e[45m' # Purple
bakcyn='\e[46m' # Cyan
bakwht='\e[47m' # White
trublk='\e[38;2;0;0;0m' # TrueColor Black
txtrst='\e[0m' # Text Reset
function echocolor {
local COLOR="$1"
local BG="$2"
shift 2
echo -e ${COLOR}${BG}"$@"${txtrst}
}
# echo with a color
function echopurple {
echo -e ${bldpur}"$@"${txtrst}
}
function echored {
echo -e ${bldred}"$@"${txtrst}
}
function echoblue {
echo -e ${bldblu}"$@"${txtrst}
}
function echogreen {
echo -e ${bldgrn}"$@"${txtrst}
}
function echocyan {
echo -e ${bldcyn}"$@"${txtrst}
}
function echoyellow {
echo -e ${bldylw}"$@"${txtrst}
}
function echowhite {
echo -e ${bldwht}"$@"${txtrst}
}
function echoblack {
echo -e ${bldblk}"$@"${txtrst}
}
# echo with color; no newline
function nechopurple {
echo -n -e ${bldpur}"$@"${txtrst}
}
function nechored {
echo -n -e ${bldred}"$@"${txtrst}
}
function nechoblue {
echo -n -e ${bldblu}"$@"${txtrst}
}
function nechogreen {
echo -n -e ${bldgrn}"$@"${txtrst}
}
function nechocyan {
echo -n -e ${bldcyn}"$@"${txtrst}
}
function nechoyellow {
echo -n -e ${bldylw}"$@"${txtrst}
}
function nechowhite {
echo -n -e ${bldwht}"$@"${txtrst}
}
function nechoblack {
echo -n -e ${bldblk}"$@"${txtrst}
}
# echodim with a color
function echodimpurple {
echo -e ${txtpur}"$@"${txtrst}
}
function echodimred {
echo -e ${txtred}"$@"${txtrst}
}
function echodimblue {
echo -e ${txtblu}"$@"${txtrst}
}
function echodimgreen {
echo -e ${txtgrn}"$@"${txtrst}
}
function echodimcyan {
echo -e ${txtcyn}"$@"${txtrst}
}
function echodimyellow {
echo -e ${txtylw}"$@"${txtrst}
}
function echodimwhite {
echo -e ${txtwht}"$@"${txtrst}
}
function echodimblack {
echo -e ${txtblk}"$@"${txtrst}
}
# echodim with color; no newline
function nechodimpurple {
echo -n -e ${txtpur}"$@"${txtrst}
}
function nechodimred {
echo -n -e ${txtred}"$@"${txtrst}
}
function nechodimblue {
echo -n -e ${txtblu}"$@"${txtrst}
}
function nechodimgreen {
echo -n -e ${txtgrn}"$@"${txtrst}
}
function nechodimcyan {
echo -n -e ${txtcyn}"$@"${txtrst}
}
function nechodimyellow {
echo -n -e ${txtylw}"$@"${txtrst}
}
function nechodimwhite {
echo -n -e ${txtwht}"$@"${txtrst}
}
function nechodimblack {
echo -n -e ${txtblk}"$@"${txtrst}
}
# echo with bg, black text
function echobgpurple {
echo -e ${bldblk}${bakpur}"$@"${txtrst}
}
function echobgred {
echo -e ${bldblk}${bakred}"$@"${txtrst}
}
function echobgblue {
echo -e ${bldblk}${bakblu}"$@"${txtrst}
}
function echobggreen {
echo -e ${bldblk}${bakgrn}"$@"${txtrst}
}
function echobgcyan {
echo -e ${bldblk}${bakcyn}"$@"${txtrst}
}
function echobgyellow {
echo -e ${bldblk}${bakylw}"$@"${txtrst}
}
function echobgwhite {
echo -e ${bldblk}${bakwht}"$@"${txtrst}
}
function echobgblack {
echo -e ${txtwht}${bakblk}"$@"${txtrst}
}
# echo with bg, black text; no new line
function nechobgpurple {
echo -e -n ${trublk}${bakpur}"$@"${txtrst}
}
function nechobgred {
echo -e -n ${trublk}${bakred}"$@"${txtrst}
}
function nechobgblue {
echo -e -n ${trublk}${bakblu}"$@"${txtrst}
}
function nechobggreen {
echo -e -n ${trublk}${bakgrn}"$@"${txtrst}
}
function nechobgcyan {
echo -e -n ${trublk}${bakcyn}"$@"${txtrst}
}
function nechobgyellow {
echo -e -n ${trublk}${bakylw}"$@"${txtrst}
}
function nechobgwhite {
echo -e -n ${trublk}${bakwht}"$@"${txtrst}
}
function nechobgblack {
echo -e -n ${txtwht}${bakblk}"$@"${txtrst}
}
# spit noticeable error
function echoerr {
echo -e ${bakwht}${bldred}"$@"${txtrst} >&2
}
function exitwitherror {
echo -e ${bakwht}${bldred}"$2"${txtrst} >&2
exit $1
}
#!/bin/bash
# cover art should be named cover.jpg in each folder
# give the terminal time to resize
sleep 0.3
source "$HOME/bin/color.sh"
# settings
MUSICFOLDER="$HOME/music"
DEFAULTART="$HOME/music/unknownartist.jpg"
# get size of terminal
COLS="$(tput cols)"
LINES="$(tput lines)"
MENULINES="$LINES"
let MENULINES=LINES-5
# art width, height, menusizes
declare -i COVERWIDTH=15
declare -i COVERHEIGHT=8
declare -i ITEMHEIGHT=0
let ITEMHEIGHT=COVERHEIGHT+2
declare -i ITEMSPERLINE=0
let ITEMSPERLINE=COLS/COVERWIDTH
declare -i ROWSTOFIT=0
let ROWSTOFIT=MENULINES/ITEMHEIGHT
declare -i MENUSIZE=0
let MENUSIZE=ROWSTOFIT*ITEMSPERLINE
MAXALBWIDTH=$((COVERWIDTH - 3))
#check if kitty
if [[ "$TERM" == "xterm-kitty" ]] ; then
KITTY="yes"
else
KITTY="no"
fi
# variable declaration
declare -a MENULIST=()
declare -a PLIST=()
declare -a SONGLIST=()
declare -a SONGLISTREFS=()
declare -i MENUOFFSET=0
MENUTYPE="main"
LPROMPTCHOICE=""
RANDOMTOKEN="Rr=A_ndD-OMm"
# AAORA = albumartist or artist
AAORA="albumartist"
####################
# FUNCTIONS
####################
# add a song from a song listing to the queue
# argument is 1-based number from song list
function addsong {
local ARG="$1"
# adjust to 0-based number
local POS=$((ARG - 1))
mpc add "${SONGLISTREFS[$POS]}"
}
# load a collection of albums by searching ARG
function albums {
local ARG="$1"
# if no argument given, ask for starting letter
if [[ -z "$ARG" ]] ; then
letter_prompt
if [[ "$LPROMPTCHOICE" == '#' ]] ; then
ARG='^[0-9]'
elif [[ "$LPROMPTCHOICE" == '?' ]] ; then
ARG="$RANDOMTOKEN"
else
ARG="^$LPROMPTCHOICE"
fi
fi
# populate list of albums for the menu
MENULIST=()
if [[ "$ARG" == "$RANDOMTOKEN" ]] ; then
# random list
mapfile -t MENULIST < <(mpc -f "%album%=-=%${AAORA}%" listall | sort | uniq | shuf | head -n "$MENUSIZE")
else
# use argument to search
mapfile -t MENULIST < <(mpc search -f "%album%=-=%${AAORA}%" "(( album =~ \"$ARG\" ))" | sort | uniq )
fi
# count number of albums loaded
MCOUNT="${#MENULIST[@]}"
if [[ "$MCOUNT" -eq 0 ]] ; then
echo "No albums found."
return
fi
# if there is only one, jump right to it
if [[ "$MCOUNT" -eq 1 ]] ; then
load_album "${MENULIST[0]}"
return
fi
# display menu with coverart
MENUTYPE="albums"
MENUOFFSET=0
art_menu
}
# load a collection of artists by searching for them
function artists {
local ARG="$1"
# turn into artists mode for finer grained albums
AAORA="artist"
# prompt for letter if search parameter is empty
if [[ -z "$ARG" ]] ; then
letter_prompt
if [[ "$LPROMPTCHOICE" == '#' ]] ; then
ARG='^[0-9]'
elif [[ "$LPROMPTCHOICE" == '?' ]] ; then
ARG="$RANDOMTOKEN"
else
ARG="^$LPROMPTCHOICE"
fi
fi
# populate list of artists
MENULIST=()
if [[ "$ARG" == "$RANDOMTOKEN" ]] ; then
mapfile -t MENULIST < <(mpc -f "%artist%" listall | sort | uniq | shuf | head -n "$MENUSIZE")
else
mapfile -t MENULIST < <(mpc search -f "%artist%" "(( artist =~ \"$ARG\" ))" | sort | uniq )
fi
# count size of list; if zero, return
MCOUNT="${#MENULIST[@]}"
if [[ "$MCOUNT" -eq 0 ]] ; then
echo "No artists found."
return
fi
# if one artist matching search, jump right to it
if [[ "$MCOUNT" -eq 1 ]] ; then
load_artist "${MENULIST[0]}"
return
fi
# display cover art menu
MENUTYPE="artists"
MENUOFFSET=0
art_menu
}
# function to display menu with cover art, either albums or artists
function art_menu {
clear # clear the screen
local ROWSDONE=0 # count how many rows filled in
# loop over rows (Y), then columns (X)
for ((Y=0;Y<ROWSTOFIT;Y++)) ; do
for ((X=0;X<ITEMSPERLINE;X++)) ; do
# find spot in array
local SPOT=0
let SPOT="MENUOFFSET+(ITEMSPERLINE*Y)+X"
# break if we are past the end of the list
if [[ "$SPOT" -ge ${#MENULIST[@]} ]] ; then
break
fi
# if we haven't broken, then add 1 to rows on first item
if [[ "$X" -eq 0 ]] ; then
let ROWSDONE=ROWSDONE+1
fi
# read info to display from the menulist
if [[ "$MENUTYPE" == "artists" ]] ; then
# in artists mode, we just have name of artist
local ARTIST="${MENULIST[$SPOT]}"
local ALBUM=''
else
# in albums mode, we have both (album-)artist and album
local FULLITEM="${MENULIST[$SPOT]}"
local ALBUM="${FULLITEM%=-=*}"
local ARTIST="${FULLITEM#*=-=}"
fi
# insert cover art
if [[ "$KITTY" == "yes" ]] ; then
local ART="$(get_menu_art "${MENULIST[$SPOT]}")"
kitty +kitten icat --place ${COVERWIDTH}x${COVERHEIGHT}@$((X * $COVERWIDTH))x$((Y * ITEMHEIGHT)) --transfer-mode file "$ART"
fi
# put cursor under art
tput cup $(( (Y * ITEMHEIGHT) + COVERHEIGHT )) $(( (X * $COVERWIDTH) ))
# item number
nechocyan "$((SPOT + 1))"
nechowhite ") "
# is album not blank, put it on first line
if [[ -n "$ALBUM" ]] ; then
nechoblue "${ALBUM:0:$MAXALBWIDTH}"
fi
# move to next line, insert artist or albumartist name
tput cup $(( (Y * ITEMHEIGHT) + COVERHEIGHT + 1 )) $(( (X * $COVERWIDTH) ))
nechoyellow "${ARTIST:0:$COVERWIDTH}"
done
done
# insert cursor below menu based on rowsdone
tput cup $(( ROWSDONE*ITEMHEIGHT )) 0
# input suggestions for this kind of menu
nechowhite "["
nechocyan "#"
nechowhite "] "
nechogreen 'choose # '
# only insert back suggestion if we're not at beginning
if [[ "$MENUOFFSET" -gt 0 ]] ; then
nechowhite "["
nechocyan "<"
nechowhite "] "
nechogreen "back "
fi
# only insert next suggestion if there are more to see
if [[ "$((SPOT + 1))" -lt "${#MENULIST[@]}" ]] ; then
nechowhite "["
nechocyan ">"
nechowhite "] "
nechogreen "more "
fi
nechowhite "["
nechocyan "m"
nechowhite "] "
echogreen "main menu"
}
# deletes items from play queue, or clears entire queue
function clearq {
local ARG="$1"
# if no argument given, or "all" given, clear everything
if [[ -z "$ARG" ]] || [[ "$ARG" =~ [Aa][Ll][Ll] ]] ; then
mpc clear
return
fi
# if there is a comma in the argument, break off first part
if [[ "$ARG" =~ , ]] ; then
local FIRSTARG="${ARG%%,*}"
local REMAINDER="${ARG#*,}"
# do first part
clearq "$FIRSTARG"
# then do remainder, which may have additional commas
clearq "$REMAINDER"
return
fi
# if there is a hyphen, then do the range
if [[ "$ARG" =~ - ]] ; then
local STARTPOS="${ARG%%-*}"
local ENDPOS="${ARG##*-}"
for ((I=ENDPOS;I>=STARTPOS;I=I-1)) ; do
mpc del "$I"
done
return
fi
# if we're here, we should have a single number
mpc del "$ARG"
}
# add items to the play queue
function enqueue {
local ARG="$1"
# if argument is blank, or contains "all", add entire songlist
if [[ -z "$ARG" ]] || [[ "$ARG" =~ [Aa][Ll][Ll] ]] ; then
ARG="1-${#SONGLIST[@]}"
fi
# break up comma-ed arguments
if [[ "$ARG" =~ , ]] ; then
local FIRSTARG="${ARG%%,*}"
local REMAINDER="${ARG#*,}"
# do part before first comma
enqueue "$FIRSTARG"
# then do the rest (may end up back here)
enqueue "$REMAINDER"
return
fi
# if there is a hyphen, apply to range
if [[ "$ARG" =~ - ]] ; then
local STARTPOS="${ARG%%-*}"
local ENDPOS="${ARG##*-}"
for ((I=STARTPOS;I<=ENDPOS;I++)) ; do
addsong "$I"
done
echo "Added to queue."
return
fi
# if we are here, then argument should be single number
addsong "$ARG"
echo "Added to queue."
}
# determines art location by file, which should just be cover.jpg in same
# folder; otherwise, return default art
function get_art_for_file {
local FIRST="$1"
local DIR="$(dirname "$FIRST")"
POSSCOVER="$MUSICFOLDER/$DIR/cover.jpg"
if [[ -e "$POSSCOVER" ]] ; then
echo "$POSSCOVER"
return
fi
# return default
echo "$DEFAULTART"
return
}
# determines art for artist or artist/album-albumartist
function get_menu_art {
local ARG="$1"
# look in folder for artist or albumartist/album if it exists
if [[ "$MENUTYPE" == "artists" ]] ; then
local AUND="$ARG"
else
local ALBUM="${ARG%=-=*}"
local ARTIST="${ARG#*=-=}"
local AUND="$ARTIST/$ALBUM"
fi
AUND="${AUND// /_}"
local POSSCOVER="$MUSICFOLDER/$AUND/cover.jpg"
if [[ -e "$POSSCOVER" ]] ; then
echo "$POSSCOVER"
return
fi
# if not found yet, look for first search hit
if [[ "$MENUTYPE" == "artists" ]] ; then
local FIRST="$(mpc search "(( $AAORA == \"$ARG\" ))" | head -n 1)" #"
else
local FIRST="$(mpc search "(( $AAORA == \"$ARTIST\" ) AND ( album == \"$ALBUM\"))" | head -n 1)" #"
fi
# look for art in folder of first hit
local DIR="$(dirname "$FIRST")"
POSSCOVER="$MUSICFOLDER/$DIR/cover.jpg"
if [[ -e "$POSSCOVER" ]] ; then
echo "$POSSCOVER"
return
fi
# if not found yet, return default
echo "$DEFAULTART"
return
}
# play a certain song in current playlist
function jump {
mpc play "$1"
viewqueue
}
# prompt for a letter, or # for numbers, or ? for random
function letter_prompt {
LPROMPTCHOICE=''
echo
echogreen '# A B C D E F G H I J K L M N O P Q R S T U V W X Y Z ?'
echo -n 'Starting with: '
while [[ -z "$LPROMPTCHOICE" ]] ; do
read -r LPROMPTCHOICE
done
}
# load a given album; argument with be [album]=-=[(album)artist]
function load_album {
ARG="$1"
# break up argument into album and (album)artist
local ALBUM="${ARG%=-=*}"
local ARTIST="${ARG#*=-=}"
# populate lists
# songlist is displayable list of songs; songlistrefs are filenames of
# same songs
SONGLIST=()
SONGLISTREFS=()
mapfile -t SONGLIST < <(mpc search -f '%artist% - %title%' "(( $AAORA == \"$ARTIST\" ) AND ( album == \"$ALBUM\"))" )
mapfile -t SONGLISTREFS < <(mpc search "(( $AAORA == \"$ARTIST\" ) AND ( album == \"$ALBUM\"))" )
# display list of songs
song_menu
}
# load a given artist, argument is name of artist
function load_artist {
local ARG="$1"
# populate list of albums for that artist
MENULIST=()
mapfile -t MENULIST < <(mpc search -f '%album%=-=%artist%' "(( artist == \"$ARG\" ))" | sort | uniq)
# count size of list; if empty, return
MCOUNT="${#MENULIST[@]}"
if [[ "$MCOUNT" -eq 0 ]] ; then
echo "No albums found for that artist."
return
fi
# if one item in album list, jump right to it
if [[ "$MCOUNT" -eq 1 ]] ; then
load_album "${MENULIST[0]}"
return
fi
# display cover art menu of albums for that artist
MENUTYPE="albums"
MENUOFFSET=0
art_menu
}
# create songlist for a given playlist; argument is name of playlist
function load_playlist {
local ARG="$1"
# songlist is displayablelist, songlistrefs is list of filenames
SONGLIST=()
SONGLISTREFS=()
mapfile -t SONGLIST < <(mpc playlist "$ARG")
mapfile -t SONGLISTREFS < <(mpc -f '%file%' playlist "$ARG")
# display list of songs
song_menu
}
# main menu gives main options
function main_menu {
clear # clear the screen
echo "Choose an option:"
# queue
nechowhite " ["
nechocyan "q"
nechowhite "]"
echogreen " View/edit queue"
# artists
nechowhite " ["
nechocyan "a"
nechowhite "]"
nechogreen " Select by artist "
echodimwhite "(with optional search parameter)"
# albums
nechowhite " ["
nechocyan "l"
nechowhite "]"
nechogreen " Select by album "
echodimwhite "(with optional search parameter)"
# songs
nechowhite " ["
nechocyan "s"
nechowhite "]"
nechogreen " Select by song title "
echodimwhite "(with optional search parameter)"
# playlists
nechowhite " ["
nechocyan "p"
nechowhite "]"
nechogreen " Select playlist "
echodimwhite "(with optional search parameter)"
# random
nechowhite " ["
nechocyan "r"
nechowhite "]"
nechogreen " Random "
echodimwhite "(albums) (or add a for artists, s for songs, etc.)"
# quit program
nechowhite " ["
nechocyan "x"
nechowhite "]"
echogreen " Exit"
# set menu type
MENUTYPE="main"
}
# navigate to previous items in cover art menu
function menu_back {
# if we are not in a cover art menu, do nothing
if [[ "$MENUTYPE" != "artists" ]] && [[ "$MENUTYPE" != "albums" ]] ; then
return
fi
# change menu offset
if [[ "$MENUOFFSET" -le "$MENUSIZE" ]] ; then
MENUOFFSET=0
else
MENUOFFSET=$((MENUOFFSET - MENUSIZE))
fi
# redisplay menu
art_menu
}
# navigate forward in cover art menu
function menu_more {
# if we are not in a cover art menu, do nothing
if [[ "$MENUTYPE" != "artists" ]] && [[ "$MENUTYPE" != "albums" ]] ; then
return
fi
# get index of last item current displayed, and how many items there are
CURREND=$((MENUOFFSET + MENUSIZE))
TOTITEMS=${#MENULIST[@]}
# we are already at the end, do nothing and say so
if [[ "$CURREND" -ge "$TOTITEMS" ]] ; then
echo "Already at end."
return
fi
# change offset
MENUOFFSET=$((MENUOFFSET + MENUSIZE))
# redisplay menu
art_menu
}
# if just a number if given what happens depends on what kind of meny
# we are looking at
function numeric_choice {
local ARG="$1"
case $MENUTYPE in
# in album menu, load the album in question
albums)
local I=$((ARG - 1))
if [[ "$I" -lt "${#MENULIST[@]}" ]] ; then
load_album "${MENULIST[$I]}"
else
echo "Not a valid choice."
fi
;;
# in artists meny, load an album menu for that artist
artists)
local I=$((ARG - 1))
if [[ "$I" -lt "${#MENULIST[@]}" ]] ; then
load_artist "${MENULIST[$I]}"
else
echo "Not a valid choice."
fi
;;
# if menu of playlists, view its songlist
playlist)
local I=$((ARG - 1))
if [[ "$I" -lt "${#PLIST[@]}" ]] ; then
load_playlist "${PLIST[$I]}"
else
echo "Not a valid choice."
fi
;;
# if viewing queue, jump to that song
queue)
jump "$ARG"
;;
# if viewing a songlist, add that song to queue
songs)
enqueue "$ARG"
;;
# otherwise, do nothing
*)
;;
esac
}
# play something immediately
function play_now {
local ARG="$1"
# clear the queue
clearq
# add what is requested back to queue and play it
enqueue "$ARG"
mpc play &> /dev/null
}
# listing of playlists, either narrowed by arg search, or all
function playlist_menu {
local ARG="$1"
# set type of meny and clear screen
MENUTYPE=playlist
clear
# get list of playlists
PLIST=()
if [[ "$ARG" == "$RANDOMTOKEN" ]] ; then
mapfile -t PLIST < <(mpc lsplaylists | shuf | head -n 5)
ARG=''
else
mapfile -t PLIST < <(mpc lsplaylists)
fi
# if argument provided, filter it
if [[ -n "$ARG" ]] ; then
local NLIST=()
for P in "${PLIST[@]}" ; do
if [[ "$P" =~ "$ARG" ]] ; then
NLIST+=($P)
fi
done
PLIST=()
PLIST="$NLIST"
fi
# count number of results
local PCOUNT="${#PLIST[@]}"
# if there is only one, go right there
if [[ "$PCOUNT" -eq 1 ]] ; then
load_playlist "${PLIST[0]}"
return
fi
# if there is none, say so
if [[ "$PCOUNT" -gt 0 ]] ; then
echo "Playlists:"
else
echo "There are no playlists."
fi
# print numbered list of found playlists
for ((I=0;I<PCOUNT;I++)) ; do
C=$((I+1))
printf -v C "%3s" "$C"
echo -n " "
nechocyan "$C"
nechowhite ") "
echoblue "${PLIST[$I]}"
done
# input suggestions for this menu
nechowhite '['
nechoblue '#'
nechowhite '] '
nechogreen 'view # '
nechowhite '['
nechoblue 'm'
nechowhite '] '
nechogreen 'main menu '
}
# save or write current queue as playlist; argument is name of playlist
function plwrite {
local ARG="$1"
# if no name given, ask for one
if [[ -z "$ARG" ]] ; then
echo -n "Playlist name: "
read -r ARG
fi
# if it works, tell them
if mpc save "$ARG" ; then echo "Saved playlist as $ARG" ; fi
}
# this is the basic prompt for input, and is basically the same everywhere
function prompt {
# prompt for and read choice
echo
echo -n "Your choice: "
read -r CHOICE
# react accordingly
case $CHOICE in
quit)
exit 0
;;
exit|x*|X*)
exit 0
;;
','|'<')
menu_back
;;
'.'|'>')
menu_more
;;
'+'*)
local ARG="${CHOICE:1}"
enqueue "$ARG"
;;
al*|l*|L*)
AAORA="albumartist"
local ARG="$(echo "$CHOICE" | sed 's/^\S*\s*//')"
albums "$ARG"
;;
a*|A*)
local ARG="$(echo "$CHOICE" | sed 's/^\S*\s*//')"
artists "$ARG"
;;
c*|C*)
local ARG="$(echo "$CHOICE" | sed 's/^[A-Za-z]*\s*//')"
clearq "$ARG"
viewqueue
;;
j*|J*)
local ARG="$(echo "$CHOICE" | sed 's/^[A-Za-z]*\s*//')"
jump "$ARG"
;;
m*|M*)
main_menu
;;
n*|N*)
local ARG="$(echo "$CHOICE" | sed 's/^[A-Za-z]*\s*//')"
play_now "$ARG"
;;
p*|P*)
local ARG="$(echo "$CHOICE" | sed 's/^\S*\s*//')"
playlist_menu "$ARG"
;;
q*|Q*)
viewqueue
;;
r*|R*)
local ARG="$(echo "$CHOICE" | sed 's/^\S*\s*//')"
random "$ARG"
;;
s*|S*)
local ARG="$(echo "$CHOICE" | sed 's/^\S*\s*//')"
songs "$ARG"
;;
w*|W*)
local ARG="$(echo "$CHOICE" | sed 's/^\S*\s*//')"
plwrite "$ARG"
;;
[0-9]*)
numeric_choice "$CHOICE"
;;
*)
;;
esac
}
# load stuff randomly, either albums (default), artists, songs
function random {
local ARG="$1"
# default to albums if no argument given
if [[ -z "$ARG" ]] ; then
ARG="l"
fi
# load appropriate kind of menu based on argument
case $ARG in
l*|al*)
AAORA="albumartist"
albums "$RANDOMTOKEN"
;;
a*)
artists "$RANDOMTOKEN"
;;
s*)
songs "$RANDOMTOKEN"
;;
p*)
playlist_menu "$RANDOMTOKEN"
;;
*)
;;
esac
}
# load a list of songs, which might be an album, or playlist, or random, etc.
function song_menu {
clear
MENUTYPE="songs"
# count list of list
SCOUNT="${#SONGLIST[@]}"
if [[ "$SCOUNT" -gt 0 ]] ; then
if [[ "$KITTY" == "yes" ]] ; then
# load art from first song
ART="$(get_art_for_file "${SONGLISTREFS[0]}")"
kitty +kitten icat --place ${COVERWIDTH}x${COVERHEIGHT}@2x1 --transfer-mode file "$ART"
tput cup "$((COVERHEIGHT + 2))" 0
fi
echo "Songs:"
else
echo "There are no songs to view."
return
fi
# actual list of songs, with numbers
for ((I=0;I<SCOUNT;I++)) ; do
echo -n " ";
N=$((I + 1))
printf -v N "%5s" "$N"
echo -n " "
nechocyan "$N"
nechowhite ") "
echoblue "${SONGLIST[$I]}"
done
# input suggests for this menu
nechowhite '['
nechocyan '+'
nechowhite '] '
nechogreen 'add (all/#) '
nechowhite '['
nechocyan 'n'
nechowhite '] '
nechogreen 'play now (all/#) '
nechowhite '['
nechocyan 'm'
nechowhite '] '
echogreen 'main menu'
}
# request a list of songs by search argument
function songs {
ARG="$1"
# if no search argument given, ask for first letter
if [[ -z "$ARG" ]] ; then
letter_prompt
if [[ "$LPROMPTCHOICE" == '#' ]] ; then
ARG='^[0-9]'
elif [[ "$LPROMPTCHOICE" == '?' ]] ; then
ARG="$RANDOMTOKEN"
else
ARG="^$LPROMPTCHOICE"
fi
fi
# songlist is displayable list of songs; songlist refs is corresponding
# filenames
# here we populate those lists
SONGLIST=()
SONGLISTREFS=()
# getsongs is a temporary array with both information in one
# we populate it first
local GETSONGS=()
if [[ "$ARG" == "$RANDOMTOKEN" ]] ; then
mapfile -t GETSONGS < <(mpc -f '%artist% - %title%:=:=:%file%' listall | sort | shuf | head -n 20)
else
mapfile -t GETSONGS < <(mpc search -f '%artist% - %title%:=:=:%file%' "(( title =~ \"$ARG\" ))")
fi
# loop through getsongs and split information into the two arrays we need
GSCOUNT="${#GETSONGS[@]}"
for ((I=0;I<GSCOUNT;I++)) ; do
local BOTH="${GETSONGS[$I]}"
local STITLE="${BOTH%:=:=:*}"
local SFILE="${BOTH#*:=:=:}"
SONGLIST[$I]="$STITLE"
SONGLISTREFS[$I]="$SFILE"
done
# display list of songs
song_menu
}
# view the current playlist
function viewqueue {
clear
MENUTYPE="queue"
# read list from mpc; get its size
local QLIST=()
mapfile -t QLIST < <(mpc playlist)
local QLENGTH="${#QLIST[@]}"
# get current filename and position
local CURRPOSITION="$(mpc current -f %position%)"
local CURRFILE="$(mpc -f '%file%' current)"
# if something playing, show the art for it
if [[ -n "$CURRFILE" ]] && [[ "$KITTY" == "yes" ]] ; then
local ART="$(get_art_for_file "$CURRFILE")"
kitty +kitten icat --place ${COVERWIDTH}x${COVERHEIGHT}@2x1 --transfer-mode file "$ART"
tput cup "$((COVERHEIGHT + 2))" 0
fi
# header at top
if [[ "$QLENGTH" -eq 0 ]] ; then
echo "The playlist is empty."
else
echo "Current playlist:"
fi
# loop over playlist items, display with number
for ((I=0;I<QLENGTH;I++)) ; do
local CURRTRACK=no
local POS=$((I + 1))
if [[ "$POS" -eq "$CURRPOSITION" ]] ; then
CURRTRACK=yes
fi
# if current, add arrow with purple background
if [[ "$CURRTRACK" == "yes" ]] ; then
nechobgpurple " → "
else
echo -n " "
fi
while [[ "${#POS}" -lt 3 ]] ; do
POS=" $POS"
done
nechocyan "$POS"
nechowhite ") "
# if current, give purple background
TT="${QLIST[$I]}"
if [[ "$CURRTRACK" == "yes" ]] ; then
nechobgpurple "$TT$(tput el)"
echo
else
echoblue "$TT"
fi
done
# Display hints for this menutype
# jump
echo
nechowhite '['
nechocyan '#'
nechowhite '] '
nechogreen 'jump to num '
# clear
nechowhite '['
nechocyan 'c(#)'
nechowhite '] '
nechogreen 'clear '
# save as playlist
nechowhite '['
nechocyan 'w'
nechowhite '] '
nechogreen 'write/save '
nechowhite '['
nechocyan 'm'
nechowhite '] '
nechogreen 'main menu'
}
######################### main loop
function prompt_loop {
main_menu
while true ; do
prompt
done
}
prompt_loop
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment