Created
February 2, 2026 22:01
-
-
Save cotko/7f97a2502d7252cf9563bbbbcdf454ec to your computer and use it in GitHub Desktop.
FZF Radio-browser search with MPD enqueue
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| API="https://de1.api.radio-browser.info/json" | |
| MODE=1 # 1 = by name, 2 = country list | |
| PREVIEW_MODE=1 # 1 = hints, 2 = item info | |
| MODE_FILE=/tmp/RADIOS_MODE_$RANDOM | |
| PREVIEW_MODE_FILE=/tmp/RADIOS_PREVIEW_MODE_$RANDOM | |
| LAST_STATIONS_FILE=/tmp/RADIOS_STATIONS_$RANDOM | |
| PORT=$((RANDOM % 16384 + 49152)) | |
| echo $MODE > "$MODE_FILE" | |
| echo $PREVIEW_MODE > "$PREVIEW_MODE_FILE" | |
| function setMode() { | |
| echo "$1" > "$MODE_FILE" | |
| } | |
| function togglePreviewMode() { | |
| PREVIEW_MODE=$(cat "$PREVIEW_MODE_FILE") | |
| PREVIEW_MODE=$((PREVIEW_MODE % 2 + 1)) | |
| echo $PREVIEW_MODE > "$PREVIEW_MODE_FILE" | |
| } | |
| function getModeInfo() { | |
| MODE=$(cat "$MODE_FILE") | |
| if [ "$MODE" -eq 1 ]; then | |
| echo "Search by name" | |
| elif [ "$MODE" -eq 2 ]; then | |
| echo "List of countries" | |
| else | |
| echo "I am broken 🥺" | |
| fi | |
| } | |
| function printModeInfo() { | |
| echo "mode = $(getModeInfo)" | |
| } | |
| function fetchStations() { | |
| local limit=100 | |
| local query="$1" | |
| local qparams="" | |
| [ "${#query}" -lt 1 ] && query="" | |
| MODE=$(cat "$MODE_FILE") | |
| if [ "$MODE" -eq 1 ]; then | |
| qparams="name=$query" | |
| else | |
| echo "I am broken 🥺" | |
| exit 1 | |
| fi | |
| if [ "${#query}" -lt 1 ]; then | |
| echo "top $limit" \ | |
| | tee "$LAST_STATIONS_FILE" | |
| else | |
| echo "query = ${query}" \ | |
| | tee "$LAST_STATIONS_FILE" | |
| fi | |
| printModeInfo \ | |
| | tee -a "$LAST_STATIONS_FILE" | |
| curl \ | |
| -s \ | |
| --data-urlencode "$qparams" \ | |
| --data-urlencode "order=votes" \ | |
| --data-urlencode "reverse=true" \ | |
| --data-urlencode "limit=$limit" \ | |
| --data-urlencode "hidebroken=true" \ | |
| "$API/stations/search" \ | |
| | jq -r ' | |
| .[] | | |
| [ | |
| (.name | gsub("\t";" ")), | |
| (((.bitrate // 0) | tostring) + "kbps"), | |
| (.url_resolved), | |
| (.bitrate // "n/a"), | |
| (.codec // "n/a"), | |
| (.country // ""), | |
| (.votes // "n/a") | |
| ] | |
| | @tsv | |
| ' \ | |
| | tee -a "$LAST_STATIONS_FILE" | |
| } | |
| function fetchCountries() { | |
| echo "" | |
| printModeInfo | |
| curl \ | |
| -s \ | |
| --data-urlencode "order=stationcount" \ | |
| --data-urlencode "reverse=true" \ | |
| "$API/countries" \ | |
| | jq -r '.[] | | |
| [ | |
| (.stationcount // "n/a"), | |
| (.name | gsub("\t";" ")) | |
| ] | |
| | @tsv' | |
| } | |
| function fetchSationsForCountry() { | |
| country="$1" | |
| echo "List of stations for $country" \ | |
| | tee "$LAST_STATIONS_FILE" | |
| printModeInfo \ | |
| | tee -a "$LAST_STATIONS_FILE" | |
| curl \ | |
| -s \ | |
| --data-urlencode "country=$country" \ | |
| --data-urlencode "order=votes" \ | |
| --data-urlencode "reverse=true" \ | |
| --data-urlencode "hidebroken=true" \ | |
| "$API/stations/search" \ | |
| | jq -r ' | |
| .[] | | |
| [ | |
| (.name | gsub("\t";" ")), | |
| (((.bitrate // 0) | tostring) + "kbps"), | |
| (.url_resolved), | |
| (.bitrate // "n/a"), | |
| (.codec // "n/a"), | |
| (.country // "n/a"), | |
| (.votes // "n/a") | |
| ] | |
| | @tsv | |
| ' \ | |
| | tee -a "$LAST_STATIONS_FILE" | |
| } | |
| function fetch() { | |
| MODE=$(cat "$MODE_FILE") | |
| if [ "$MODE" -eq 1 ]; then | |
| fetchStations "$1" | |
| elif [ "$MODE" -eq 2 ]; then | |
| fetchCountries | |
| else | |
| echo "I am broken 🥺" | |
| exit 1 | |
| fi | |
| } | |
| function handle() { | |
| MODE=$(cat "$MODE_FILE") | |
| if [ "$MODE" -eq 1 ]; then | |
| url=$(echo "$1" | cut -f3) | |
| mpdAddStation "$url" | |
| cat "$LAST_STATIONS_FILE" | |
| elif [ "$MODE" -eq 2 ]; then | |
| country=$(echo "$1" | cut -f2) | |
| fetchSationsForCountry "$country" | |
| # use TCP to send "clear-query" command | |
| curl -s -X POST --data 'clear-query' "http://127.0.0.1:$PORT" | |
| setMode 1 | |
| else | |
| echo "I am broken 🥺" | |
| exit 1 | |
| fi | |
| } | |
| function handleAddAndPlay() { | |
| MODE=$(cat "$MODE_FILE") | |
| if [ "$MODE" -eq 1 ]; then | |
| cat "$LAST_STATIONS_FILE" | |
| url=$(echo "$1" | cut -f3) | |
| mpdAddStation "$url" 1 | |
| fi | |
| } | |
| function mpdAddStation() { | |
| url="$1" | |
| play="$2" | |
| mpc add "$url" &>/dev/null | |
| if [ "$play" -eq 1 ]; then | |
| mpc play $(mpc playlist | wc -l) &>/dev/null | |
| fi | |
| } | |
| function preview() { | |
| PREVIEW_MODE=$(cat "$PREVIEW_MODE_FILE") | |
| if [ "$PREVIEW_MODE" -eq 1 ]; then | |
| echo 'ctrl+c for Country list mode' | |
| echo 'ctrl+s for Search mode' | |
| echo 'ctrl+x to excute query in search mode' | |
| echo 'alt+enter add station and play it' | |
| else | |
| MODE=$(cat "$MODE_FILE") | |
| if [ "$MODE" -eq 1 ]; then | |
| name=$(echo "$1" | cut -f1) | |
| country=$(echo "$1" | cut -f6) | |
| votes=$(echo "$1" | cut -f7) | |
| codec=$(echo "$1" | cut -f5) | |
| bitrate=$(echo "$1" | cut -f4) | |
| url=$(echo "$1" | cut -f3) | |
| echo "$country" | |
| echo "$name" | |
| echo "codec $codec | bitrate $bitrate | votes $votes" | |
| echo "$url" | |
| else | |
| name=$(echo "$1" | cut -f2) | |
| count=$(echo "$1" | cut -f1) | |
| echo "$name" | |
| echo "station count: $count" | |
| fi | |
| fi | |
| } | |
| export API | |
| export DELAY | |
| export MODE_FILE | |
| export PREVIEW_MODE_FILE | |
| export LAST_STATIONS_FILE | |
| export PORT | |
| export MODE | |
| export -f handle | |
| export -f handleAddAndPlay | |
| export -f fetch | |
| export -f fetchCountries | |
| export -f fetchStations | |
| export -f fetchSationsForCountry | |
| export -f setMode | |
| export -f getModeInfo | |
| export -f printModeInfo | |
| export -f preview | |
| export -f togglePreviewMode | |
| export -f mpdAddStation | |
| cleanup() { | |
| rm $MODE_FILE &>/dev/null || true | |
| rm $PREVIEW_MODE_FILE &>/dev/null || true | |
| rm $LAST_STATIONS_FILE &>/dev/null || true | |
| } | |
| trap cleanup EXIT | |
| fzf \ | |
| --ansi \ | |
| --listen "127.0.0.1:$PORT" \ | |
| --delimiter="\t" \ | |
| --with-nth="1,2" \ | |
| --layout=reverse \ | |
| --border=none \ | |
| --highlight-line \ | |
| --cycle \ | |
| --list-border=rounded \ | |
| --info=right \ | |
| --header-lines=2 \ | |
| --preview="preview {}" \ | |
| --preview-window=bottom:4:wrap \ | |
| --preview-border=line \ | |
| --preview-label="ctrl+d to toggle preview mode" \ | |
| --prompt="> " \ | |
| --bind "start:reload:bash -c 'fetch'" \ | |
| --bind 'ctrl-s:execute-silent(bash -c "setMode 1")+reload(bash -c "fetch \"\$1\"" _ {q})' \ | |
| --bind 'ctrl-c:execute-silent(bash -c "setMode 2")+reload(bash -c "fetch \"\$1\"" _ {q})' \ | |
| --bind 'ctrl-x:reload(bash -c "fetch \"\$1\"" _ {q})+clear-query' \ | |
| --bind 'ctrl-d:execute-silent(bash -c "togglePreviewMode")+refresh-preview' \ | |
| --bind 'enter:reload(bash -c "handle {}")' \ | |
| --bind 'alt-enter:execute-silent(bash -c "handleAddAndPlay {}")' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment