Files
fuzique/fuzzique
2025-07-17 07:30:39 +02:00

189 lines
6.4 KiB
Bash
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/sh
set -eu
if [ "${1:-}" = "--info" ]; then
shift
file="$1"
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"
exit 0
fi
if [ "${1:-}" = "--preview" ]; then
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'
} | batcat --color always --number --language md
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')
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="👉"
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\n" "$pnt" "$title" "$release" "$artist" "$file"
done | grep '.' | column -t -s '|'
exit 0
fi
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 <<EOF
Usage: \`$0 [ --help | [ MUSIC_DIRECTORY [ --reset-cache ] ] ]\`
The \`MUSIC_DIRECTORY\` is the path to a folder with the following tree
structure: \`./<artist>/<album>/<track>\`. 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 MayDecember.m4a
   └── cover.jpg
\`\`\`
[OPTIONS]
\`--help\`: Show this help and exit
\`--reset-cache\`: Reset cache for specified \`MUSIC_DIRECTORY\`
EOF
exit 0
fi
[ "${1:-}" ] && ROOT="$1" && shift
[ ! -d "${ROOT:-}" ] && echo "Faild to recognize music directory. Run $0 --help." && exit 1
ROOT=${ROOT%%/}
CACHE="$HOME/.cache/fuzzique"
ROOTHASH=$(echo "$ROOT" | sha1sum | cut -d ' ' -f 1)
CACHE="$CACHE/$ROOTHASH"
DEPTH=$(echo "$ROOT" | awk -F'/' '{ print NF }')
FD=/usr/bin/fdfind
FZF=fzf
MPV=mpv
MPV_SOCKET="$(mktemp --suffix=.sock)"
ARTIST_COLOR='\033[38;5;202m'
RELEASE_COLOR='\033[38;5;208m'
TRACK_COLOR='\033[38;5;215m'
OFF="\033[m"
AFMT="🎤 ${ARTIST_COLOR}%s${OFF}"
RFMT="💽 ${RELEASE_COLOR}%s${OFF}"
TFMT="🎵 ${TRACK_COLOR}%s${OFF}"
export ROOT MPV_SOCKET AFMT RFMT TFMT
# Load
artists_file="$CACHE/artists"
release_file="$CACHE/release"
tracks_file="$CACHE/tracks"
if [ "${1:-}" = "--build-db" ] || [ ! -f "$artists_file" ]; then
rm -rf "$CACHE"
mkdir -p "$CACHE"
[ ! -d "$CACHE" ] && echo "Failed to create cache directory" && exit 1
{
$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 |
awk \
-F'/' \
-v afmt="$AFMT" \
-v rfmt="$RFMT" \
-v tfmt="$TFMT" \
-v depth="$DEPTH" \
-v artists_file="$artists_file" \
-v release_file="$release_file" \
-v tracks_file="$tracks_file" \
'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 >> artists_file }
NF == depth + 2 { print rl, $0 >> release_file }
NF == depth + 3 { print tr, $0 >> tracks_file }
'
# NF == depth + 1 { print ar, $0 >> artists_file }
# NF == depth + 2 { print rl "|" ar, $0 >> release_file }
# NF == depth + 3 { print tr "|" rl "|" ar, $0 >> tracks_file }
fi
$MPV --no-config --no-terminal --input-ipc-server="$MPV_SOCKET" --idle &
$FZF \
--reverse \
--ansi \
--no-sort \
--delimiter="\t" \
--with-nth="{1}" \
--cycle \
--info=inline-right \
--info-command="$0 --info {2}" \
--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\"" \
--bind="alt-t:show-input+reload:cat \"$tracks_file\" | column -t -s '|' -E 0" \
--bind="alt-r:show-input+reload:cat \"$release_file\" | column -t -s '|'" \
--bind="alt-a:show-input+reload:cat \"$artists_file\" | column -t -s '|'" \
--bind="ctrl-l:show-input+transform:
d=\$(echo {2} | awk -F'/' '{ print NF }')
[ \"\$d\" -eq \"$((DEPTH + 1))\" ] && echo \"reload:grep -F {2}/ \\\"$release_file\\\" | column -t -s '|' || true\"
[ \"\$d\" -eq \"$((DEPTH + 2))\" ] && echo \"reload:grep -F {2}/ \\\"$tracks_file\\\" | column -t -s '|' || true\"
" \
--bind="ctrl-h:show-input+transform:
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\\\" | column -t -s '|' || true\"
[ \"\$d\" -eq \"$((DEPTH + 2))\" ] && echo \"reload:grep -F \\\"\$p/\\\" \\\"$artists_file\\\" | column -t -s '|' || true\"
" \
--bind="ctrl-p:hide-input+reload:$0 --show-playlist" \
--preview="$0 --preview {2}" \
--bind="alt-/:toggle-preview" \
<"$artists_file" || true
printf '{ "command": ["quit"] }\n' | socat - "$MPV_SOCKET"
rm -f "$MPV_SOCKET"