#!/bin/sh set -eu APP_NAME="fuzique" if [ "${1:-}" = "--prompt" ]; then shift file="$1" shift mode="$1" case "$mode" in "$MODE_SEARCH") printf "search> " ;; "$MODE_BROWSE") rel=${file#"$ROOT/"} artist=$(echo "$rel" | cut -d "/" -f 1) release=$(echo "$rel" | cut -d "/" -f 2) d=$(echo "$rel" | awk -F'/' '{ print NF }') [ "$d" -eq 2 ] && printf " $AFMT 〉 " "$artist" [ "$d" -eq 3 ] && printf " $AFMT 》 $RFMT 〉 " "$artist" "$release" ;; "$MODE_PLAYLIST") printf "" ;; esac exit 0 fi if [ "${1:-}" = "--preview" ]; then echo "call to --preview" >>/tmp/foo shift file="$1" tags=$(ffprobe -v quiet -show_entries format -of json "$file" | jq) { echo "## Lyrics" echo "" printf "%s" "$tags" | jq -C -r '.format.tags.LYRICS' } | $CAT exit 0 fi if [ "${1:-}" = "--show-playlist" ]; then [ ! -S "${MPV_SOCKET:-}" ] && exit 1 count=$(echo "{ \"command\": [\"expand-text\",\"\${playlist/count}\"] }" | socat - "$MPV_SOCKET" | jq -r '.data') if [ "$count" -eq 0 ]; then printf "(empty playlist)\t\t%s\n" "$MODE_PLAYLIST" exit 0 fi for i in $(seq 0 $((count - 1))); do file=$(echo "{ \"command\": [\"expand-text\",\"\${playlist/$i/filename}\"] }" | socat - "$MPV_SOCKET" | jq -r '.data') curr=$(echo "{ \"command\": [\"expand-text\",\"\${playlist/$i/current}\"] }" | socat - "$MPV_SOCKET" | jq -r '.data') pnt=" " [ "$curr" = "yes" ] && pnt="$PLAYLIST_POINTER" artist=$(echo "$file" | rev | cut -d "/" -f 3 | rev) release=$(echo "$file" | rev | cut -d "/" -f 2 | rev) title=$(echo "$file" | rev | cut -d "/" -f 1 | rev | sed 's/\..*$//') artist=$(printf "$AFMT" "$artist") release=$(printf "$RFMT" "$release") title=$(printf "$TFMT" "$title") printf "%s|%s|%s|%s\t%s\t%s\n" "$pnt" "$title" "$release" "$artist" "$file" "$MODE_PLAYLIST" done | grep '.' | column -t -s '|' exit 0 fi # Tools command -v "fdfind" >/dev/null && FD="fdfind" || (err "Did not find \`fdfind\`." && exit 1) command -v "fzf" >/dev/null && FZF="fzf" || (err "Did not find \`fzf\`." && exit 1) command -v "mpv" >/dev/null && MPV="mpv" || (err "Did not find \`mpv\`." && exit 1) if command -v "bat" >/dev/null; then CAT="bat" elif command -v "batcat" >/dev/null; then CAT="batcat" fi CAT=${CAT:+$CAT --color=always --style=plain --language=md} CAT=${CAT:-cat} export CAT if [ "${1:-}" = "--help" ]; then $CAT <//\`. For instance: \`\`\`sh $ tree /media/Musik /media/Musik/Mos Def └── Black on Both Sides    ├── 01 Fear Not of Man.m4a    ├── 02 Hip Hop.m4a    ├── 03 Love.m4a    ├── 04 Ms. Fat Booty.m4a    ├── 05 Speed Law.m4a    ├── 06 Do It Now.m4a    ├── 07 Got.m4a    ├── 08 Umi Says.m4a    ├── 09 New World Water.m4a    ├── 10 Rock n Roll.m4a    ├── 11 Know That.m4a    ├── 12 Climb.m4a    ├── 13 Brooklyn.m4a    ├── 14 Habitat.m4a    ├── 15 Mr. Nigga.m4a    ├── 16 Mathematics.m4a    ├── 17 May‐December.m4a    └── cover.jpg \`\`\` [OPTIONS] \`--help\`: Show this help and exit \`--reset-cache\`: Reset cache for specified \`MUSIC_DIRECTORY\` EOF exit 0 fi # Load configuration [ "${1:-}" ] && ROOT="$(realpath "$1")" && shift ROOT=${ROOT%%/} [ ! -d "${ROOT:-}" ] && echo "Faild to recognize music directory. Run $0 --help." && exit 1 DEPTH=$(echo "$ROOT" | awk -F'/' '{ print NF }') # Cache support ROOTHASH=$(echo "$ROOT" | sha1sum | cut -d ' ' -f 1) CACHE_DIR="$HOME/.cache/$APP_NAME" CACHE_FILE="$CACHE_DIR/$ROOTHASH" # Theme ARTIST_COLOR=${ARTIST_COLOR:-'\033[38;5;202m'} RELEASE_COLOR=${RELEASE_COLOR:-'\033[38;5;208m'} TRACK_COLOR=${TRACK_COLOR:-'\033[38;5;215m'} OFF="\033[m" AFMT="${AFTM:-"🎤 ${ARTIST_COLOR}%s${OFF}"}" RFMT="${RFMT:-"💽 ${RELEASE_COLOR}%s${OFF}"}" TFMT="${TFMT:-"🎵 ${TRACK_COLOR}%s${OFF}"}" PLAYLIST_POINTER="${PLAYLIST_POINTER:-☞}" #PLAYLIST_POINTER="${PLAYLIST_POINTER:-👉}" # Modes MODE_BROWSE="B" MODE_SEARCH="S" MODE_PLAYLIST="P" # Make socket MPV_SOCKET="$(mktemp --suffix=.sock)" # Export export ROOT MPV_SOCKET AFMT RFMT TFMT PLAYLIST_POINTER MODE_SEARCH MODE_BROWSE MODE_PLAYLIST # Load if [ "${1:-}" = "--reset-cache" ] || [ ! -f "$CACHE_FILE" ]; then rm -f "$CACHE_FILE" [ ! -d "$CACHE_DIR" ] && mkdir -p "$CACHE_DIR" { $FD --max-depth 2 --type d . "$ROOT" $FD --exact-depth 3 --type f -i -e "mp3" -e "mp4" -e "m4a" -e "ogg" -e "flac" -e "wav" . "$ROOT" } | sort >"$CACHE_FILE" fi # Parse artists_file_browse=$(mktemp) artists_file_search=$(mktemp) release_file_browse=$(mktemp) release_file_search=$(mktemp) tracks_file_browse=$(mktemp) tracks_file_search=$(mktemp) awk \ -F'/' \ -v afmt="$AFMT" \ -v rfmt="$RFMT" \ -v tfmt="$TFMT" \ -v depth="$DEPTH" \ -v artists_file_browse="$artists_file_browse" \ -v release_file_browse="$release_file_browse" \ -v tracks_file_browse="$tracks_file_browse" \ -v artists_file_search="$artists_file_search" \ -v release_file_search="$release_file_search" \ -v tracks_file_search="$tracks_file_search" \ -v mode_browse="$MODE_BROWSE" \ -v mode_search="$MODE_SEARCH" \ 'BEGIN { OFS="\t" iartist = depth + 1 irelease = depth + 2 itrack = depth + 3 } NF >= depth + 1 { ar = sprintf(afmt, $iartist) } NF >= depth + 2 { rl = sprintf(rfmt, $irelease) } NF >= depth + 3 { tr = $itrack; gsub(/\..*$/, "", tr); tr = sprintf(tfmt, tr) } NF == depth + 1 { print ar, $0, mode_browse >> artists_file_browse } NF == depth + 2 { print rl, $0, mode_browse >> release_file_browse } NF == depth + 3 { print tr, $0, mode_browse >> tracks_file_browse } NF == depth + 1 { print ar, $0, mode_search >> artists_file_search } NF == depth + 2 { print rl "|" ar, $0, mode_search >> release_file_search } NF == depth + 3 { print tr "|" rl "|" ar, $0, mode_search >> tracks_file_search } ' <"$CACHE_FILE" $MPV --no-config --no-terminal --input-ipc-server="$MPV_SOCKET" --idle & $FZF \ --reverse \ --ansi \ --no-sort \ --delimiter="\t" \ --with-nth="{1}" \ --cycle \ --bind="ctrl-d:half-page-down,ctrl-u:half-page-up" \ --bind="enter:execute:printf '{ \"command\": [\"loadfile\", \"%s\"] }\n' {2} | socat - \"$MPV_SOCKET\"" \ --bind="alt-enter:execute:printf '{ \"command\": [\"loadfile\", \"%s\", \"append-play\"] }\n' {2} | socat - \"$MPV_SOCKET\"" \ --preview="$0 --preview {2}" \ --bind="alt-1:reload:column -t -s '|' -E 0 \"$artists_file_search\"" \ --bind="alt-2:reload:column -t -s '|' -E 0 \"$release_file_search\"" \ --bind="alt-3:reload:column -t -s '|' -E 0 \"$tracks_file_search\"" \ --bind="ctrl-p:reload:$0 --show-playlist" \ --bind="load:transform: echo \"l2:{}\" >> /tmp/foo [ {3} = \"$MODE_SEARCH\" ] && printf \"+hide-preview\" || printf \"+show-preview\" [ {3} = \"$MODE_PLAYLIST\" ] && printf \"+hide-input\" || printf \"+show-input\" echo \"l3:{}\" >> /tmp/foo printf \"+transform-prompt:$0 --prompt {2} {3}\" echo \"l4:{}\" >> /tmp/foo echo \"load ended..\" >> /tmp/foo " \ --bind="ctrl-l:transform: printf \"clear-query+pos(1)\" d=\$(echo {2} | awk -F'/' '{ print NF }') [ \"\$d\" -eq \"$((DEPTH + 1))\" ] && echo \"+reload:grep -F {2}/ \\\"$release_file_browse\\\" | column -t -s '|' || true\" [ \"\$d\" -eq \"$((DEPTH + 2))\" ] && echo \"+reload:grep -F {2}/ \\\"$tracks_file_browse\\\" | column -t -s '|' || true\" " \ --bind="ctrl-h:transform: printf \"clear-query+pos(1)\" d=\$(echo {2} | awk -F'/' '{ print NF }') p=\$(echo {2} | rev | cut -d '/' -f 3- | rev) [ \"\$d\" -eq \"$((DEPTH + 3))\" ] && echo \"+reload:grep -F \\\"\$p/\\\" \\\"$release_file_browse\\\" | column -t -s '|' || true\" [ \"\$d\" -eq \"$((DEPTH + 2))\" ] && echo \"+reload:grep -F \\\"\$p/\\\" \\\"$artists_file_browse\\\" | column -t -s '|' || true\" " \ <"$artists_file_search" || true printf '{ "command": ["quit"] }\n' | socat - "$MPV_SOCKET" rm -f "$MPV_SOCKET" \ "$artists_file_browse" \ "$artists_file_search" \ "$release_file_browse" \ "$release_file_search" \ "$tracks_file_browse" \ "$tracks_file_search"