You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

fzmp 4.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. #!/usr/bin/env bash
  2. declare -r esc=$(printf '\033')
  3. declare -r c_reset="${esc}[0m"
  4. declare -r c_red="${esc}[31m"
  5. declare -r track_format='[[[%artist% / ][[(%date%) ]%album% / ][%track%] - [%title%]]|%file%]'
  6. usage() {
  7. LESS=-FEXR less <<HELP
  8. fzmp [OPTIONS]
  9. -A --all search all songs in the library (or F1 when running)
  10. -a --artist search artist then filter by album (or F2 when running)
  11. -p --playlist search the current playlist (or F3 when running)
  12. -h --help print this help
  13. HELP
  14. }
  15. isRunning() {
  16. pgrep "$1" &> /dev/null
  17. }
  18. err() {
  19. printf "${c_red}%s${c_reset}\n" "$*" >&2
  20. }
  21. die() {
  22. [[ -n "$1" ]] && err "$*"
  23. exit 1
  24. }
  25. has() {
  26. local verbose=false
  27. if [[ $1 == '-v' ]]; then
  28. verbose=true
  29. shift
  30. fi
  31. for c in "$@"; do c="${c%% *}"
  32. if ! command -v "$c" &> /dev/null; then
  33. [[ "$verbose" == true ]] && err "$c not found"
  34. return 1
  35. fi
  36. done
  37. }
  38. fzf() {
  39. command fzf -e -i --reverse --cycle --inline-info "$@"
  40. }
  41. filterAllSongs() {
  42. local choice
  43. mapfile -t choice < <( mpc search -f "%file%\t$track_format" filename '' |
  44. fzf --multi \
  45. --with-nth='2..' \
  46. --delimiter='\t' \
  47. --expect='f2,f3,enter' |
  48. cut -f1)
  49. case "${choice[0]}" in
  50. 'f2') filterByArtist ;;
  51. 'f3') filterPlaylist ;;
  52. 'enter') printf '%s\n' "${choice[@]:1}" | playSongs ;;
  53. esac
  54. }
  55. filterByArtist() {
  56. local choice
  57. mapfile -t choice < <(mpc list artist | sort -h | fzf --expect='f1,f3,enter')
  58. (( "${#choice[@]}" > 0 )) || die
  59. case "${choice[0]}" in
  60. 'f1') filterAllSongs ;;
  61. 'f3') filterPlaylist ;;
  62. 'enter') filterByAlbumFromArtist "${choice[1]}" ;;
  63. esac
  64. }
  65. filterByAlbumFromArtist() {
  66. local album artist choice
  67. [[ -z "$1" ]] && filterByArtist
  68. artist="$1"
  69. mapfile -t choice < <(mpc search -f '[(%date%)\t][%album%]' artist "${artist}" |
  70. sort -h | uniq |
  71. fzf --prompt="$artist > " \
  72. --expect='f1,f3,enter' \
  73. --bind='Ctrl-A:select-all' |
  74. cut -f2)
  75. case "${choice[0]}" in
  76. 'f1') filterAllSongs ;;
  77. 'f3') filterPlaylist ;;
  78. 'enter') filterSongsFromAlbum "$artist" "${choice[1]}" ;;
  79. *) filterByArtist ;;
  80. esac
  81. }
  82. filterSongsFromAlbum() {
  83. local album artist choice
  84. [[ -z "$1" || -z "$2" ]] && die
  85. artist="$1"
  86. album="$2"
  87. mapfile -t choice < <(mpc search -f '%file%\t[[[%track% - ][%title%]]|%file%]' artist "${artist}" album "${album}" |
  88. fzf --prompt="$artist - $album > " \
  89. --multi \
  90. --with-nth='2..' \
  91. --delimiter='\t' \
  92. --expect='f1,f3,enter' \
  93. --bind='Ctrl-A:select-all' |
  94. cut -f1)
  95. case "${choice[0]}" in
  96. 'f1') filterAllSongs ;;
  97. 'f3') filterPlaylist ;;
  98. 'enter') printf '%s\n' "${choice[@]:1}" | playSongs ;;
  99. *) filterByAlbumFromArtist "$artist" ;;
  100. esac
  101. }
  102. filterPlaylist() {
  103. local choice
  104. current_song=$(mpc current -f "$track_format")
  105. mapfile -t choice < <(mpc playlist -f "%position%\t$track_format" |
  106. fzf --prompt='playlist > ' \
  107. --multi \
  108. ${current_song:+--header="now playing: ${current_song}"} \
  109. --delimiter='\t' \
  110. --with-nth='2..' \
  111. --expect='f1,f2,>,<,ctrl-d,enter' |
  112. cut -f1) || die
  113. case "${choice[0]}" in
  114. 'f1') filterAllSongs ;;
  115. 'f2') filterByArtist ;;
  116. '>') mpc -q next ; filterPlaylist ;;
  117. '<') mpc -q prev ; filterPlaylist ;;
  118. 'ctrl-d') [[ -n "${choice[1]}" ]] && mpc -q del "${choice[@]:1}" ; filterPlaylist ;;
  119. 'enter') [[ -n "${choice[1]}" ]] && mpc -q play "${choice[@]:1}" ; filterPlaylist ;;
  120. esac
  121. }
  122. playSongs() {
  123. mapfile -t songs
  124. (( "${#songs[@]}" > 0 )) || die
  125. printf '%s\n' "${songs[@]}" | mpc -q add
  126. index=$(mpc playlist | wc -l)
  127. (( ${#songs[@]} > 1 )) &&
  128. index=$(( index - ${#songs[@]} + 1))
  129. mpc -q play "$index"
  130. filterPlaylist
  131. }
  132. declare filter='filterAllSongs'
  133. while :; do
  134. case "$1" in
  135. -A|--all) filter='filterAllSongs' ; shift ;;
  136. -a|--artist) filter='filterByArtist' ; shift ;;
  137. -p|--playlist) filter='filterPlaylist' ; shift ;;
  138. -h|--help) usage ; exit ;;
  139. *) break
  140. esac
  141. done
  142. has -v fzf mpc || die
  143. isRunning mpd || [[ -n "$MPD_HOST" ]] || die "can't connect to mpd"
  144. $filter