about summary refs log tree commit diff stats
path: root/radish
blob: 3cc4eb7659e46a75c1e34bbe15f09ccca78783d5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
#!/bin/sh
# RADISH
# a new and improved RADIO that can play local files and noise.
# XXX: WIP

usage() {
	cat <<EOF
RADISH: radio, music, static
USAGE:	radish [-h|-k|-r|-s|-S]
	radish -l [NAME]
	radish [STATION]

FLAGS:
 -h		Show this help and exit.
 -s		Show radish's status and exit.
 -S		Show radish's status indefinitely.
 -k		Kill the currently-playing radish invocation.
 -r		Replay most recently-played station.

OPTIONS:
 -l [NAME]	List available stations.
		If NAME is given, narrow the list to those matching it.

PARAMETERS:
 STATION	Which configured station to play.
		Stations are defined in the \$RADISH_STATION_FILE
		(default: $RADISH_STATION_FILE).
		If STATION is not present, or if it matches
		more than one station, radish will present a menu.
EOF
	exit ${1:-0}
}

config() {
	: "${RADISH_STATION_FILE:=${XDG_CONFIG_HOME:-$HOME/.config}/radish/stations}"
	RADISH_PID_FILE=/tmp/radish.pid
	RADISH_STATUS_FILE=/tmp/radish.status
	RADISH_LP_FILE="${XDG_CACHE_HOME:-$HOME/.cache}/radish.lp"
}

main() {
	config
	while getopts :hkrsSl: opt; do
		case "$opt" in
		h) usage ;;
		k) radish_kill ;;
		r) set -- "$(cat "$RADISH_LP_FILE")" ;;
		s) radish_status ;;
		S) radish_status -follow ;;
		l) radish_list "$OPTARG" ;;
		:)
			case "$OPTARG" in
			l) radish_list ;;
			*)
				echo >&2 "Option -$OPTARG requires an argument"
				usage 1
				;;
			esac
			;;
		\?)
			echo >&2 "Unknown option: -$OPTARG"
			usage 1
			;;
		esac
	done
	shift "$((OPTIND - 1))"
	radish_play "$@"
}

cleanup() {
	rm /tmp/radish.*
}

### "Private" functions

_radish_player() {
	command -v "${1:-}" ||
		command -v "${RADISH_PLAYER:-}" ||
		command -v mpv ||
		command -v vlc ||
		{
			echo >&2 "No suitable player found."
			echo >&2 "Set \$RADISH_PLAYER."
			exit 3
		}
}

_radish_stations() {
	if [ -f "$RADISH_STATION_FILE" ]; then
		sed -e '/^#/d' "$RADISH_STATION_FILE"
	else
		echo noise:
	fi
}

_radish_kill_impl() {
	[ -f "$RADISH_PID_FILE" ] || return 1
	x=
	while xargs -a "$RADISH_PID_FILE" kill 2>/dev/null; do
		case "$x" in
		xxx*)
			printf .
			x=
			;;
		*) x=x$x ;;
		esac
	done
	echo
}

_radish_desc() {
	cut -f2 "${1:--}" 2>/dev/null
}
_radish_url() {
	cut -f1 "${1:--}" 2>/dev/null
}
_radish_tags() {
	cut -f3 "${1:--}" 2>/dev/null
}

_radish_not_running() {
	echo >&2 "Running radish not found."
	exit 2
}

echo() { printf '%s\n' "$*"; }

### Main functionality

radish_kill() {
	if [ -f "$RADISH_PID_FILE" ]; then
		printf >&2 '%s' "Killing radish..."
		_radish_kill_impl
	else
		_radish_not_running
	fi
	cleanup
	exit
}

radish_status() {
	trap 'echo;exit 0' INT
	if [ -n "${1:-}" ]; then
		follow=-f
	else
		follow=
	fi
	tail $follow "$RADISH_STATUS_FILE" 2>/dev/null || _radish_not_running
	echo
	exit
}

radish_list() {
	_radish_stations | grep -i "${1:-}" | awk -F '\t' '{
		desc = $'$_schema_desc'
		url = $'$_schema_url'
		tags = $'$_schema_tags'
		printf "%-23s |", substr(desc,1,20) (length(desc)>20?"...":"")
		printf "%-23s |", substr(tags,1,20) (length(tags)>20?"...":"")
		printf "%-23s\n", substr(url,1,20) (length(url)>20?"...":"")
		}'
	exit
}

radish_choose_station() {
	cands="$(_radish_stations | grep -i "$1")"
	[ -z "$cands" ] && cands="$(_radish_stations)"

	if [ "$(echo "$cands" | wc -l)" -gt 1 ]; then
		echo "$cands" | _radish_desc | cat -n -
		while true; do
			printf "Radish> "
			read station
			if (echo "$station" | grep -qE '^[0-9]+$'); then
				station="$(echo "$cands" | sed -n "${station}p;${station}q")"
				if [ -z "$station" ]; then
					echo >&2 "No station with that number."
					continue
				else
					break
				fi
			fi
			echo >&2 "Please input a station number."
		done
	else
		station="$(echo "$cands")"
	fi
	echo >&2 "Selected: $(echo "$station" | _radish_desc)"
	station="$(echo "$station" | _radish_url)"
}

### Player functions

radish_play() {
	case "${1:-}" in
	https:* | http:*)
		_radish_kill_impl || :
		name="$(grep "$1" "$RADISH_STATION_FILE" | cut -f2)"
		echo >&2 "Streaming ${name:-$1}..."
		radish_play_web "$1"
		;;
	noise:*)
		_radish_kill_impl || :
		echo >&2 "Playing noise: ${1#noise:}"
		radish_play_noise "${1:-}"
		;;
	shuf:*)
		_radish_kill_impl || :
		echo >&2 "Shuffling from ${1#shuf:}"
		SHUF=1 radish_play_file "$1" 2>&1
		;;
	file:*)
		_radish_kill_impl || :
		echo >&2 "Playing from ${1#file:}"
		SHUF=0 radish_play_file "$1" 2>&1
		;;
	*)
		if [ -f "$RADISH_STATION_FILE" ]; then
			radish_choose_station "${1:-}"
			_radish_kill_impl || :
			radish_play "$station"
		else
			_radish_kill_impl || :
			radish_play_noise
		fi
		;;
	esac

	echo "${1:-}" | _radish_url >"$RADISH_LP_FILE"
	exit
}

radish_play_web() {
	"$(_radish_player)" "$1" >"$RADISH_STATUS_FILE" 2>&1 &
	echo $! >"$RADISH_PID_FILE"
}

radish_play_file() {
	dir="$(echo "$1" | sed 's@^[^:]*:\(//\)\?@@')"
	find "$dir" -depth -print0 \
		-iname '*flac' -o \
		-iname '*mp3' -o \
		-iname '*ogg' -o \
		-iname '*opus' |
		xargs -0 realpath |
		case "$SHUF" in
		1) shuf ;;
		0) sort ;;
		esac >/tmp/radish.m3u
	player="$(_radish_player)"
	case "$player" in
	*vlc | *mpv) args="--no-video" ;;
	*) args= ;;
	esac
	"$player" $args /tmp/radish.m3u "$1" >"$RADISH_STATUS_FILE" 2>&1 &
	echo $! >"$RADISH_PID_FILE"
}

radish_play_noise() {
	# REQUIRES PLAY (from SOX) -- based on https://gist.github.com/rsvp/1209835
	play="$(command -v play)" || {
		echo >&2 "Noise playback requires sox(1)."
		exit 3
	}
	# URL format: noise:type/center?time=60;wave=0.033;volume=1
	url_format='noise:\([^/]\+\)/\([^?]\+\)?\(.*\)'
	noise_type="$(echo "${1:-}" | sed -n "s@${url_format}@\1@")"
	noise_center="$(echo "${1:-}" | sed -n "s@${url_format}@\2@")"
	noise_params="$(echo "${1:-}" | sed -n "s@${url_format}@\3@")"
	time=60
	wave=0.03333333
	volume=1
	if [ -z "$noise_type" ] && [ -z "$noise_center" ]; then
		noise_type=brown
		noise_center=1786
	elif [ -z "$noise_type" ] || [ -z "$noise_center" ]; then
		echo >&2 "URL format: 'noise:TYPE/CENTER?[PARAMS]"
		echo >&2 "TYPE is one of 'brown','white','pink','tpdf'."
		echo >&2 "CENTER is the center of the band-pass filter."
		echo >&2 "PARAMS are TIME to generate noise;"
		echo >&2 "WAVE, denoting volume variation; and VOLUME."
		exit 4
	else
		eval "$noise_params"
	fi

	export RADISH_PLAYER=play
	echo >&2 "$(_radish_player)" \
		-c 2 --null synth "$time" "${noise_type}noise" \
		band -n "$noise_center" 499 \
		tremolo "$wave" 43 reverb 19 \
		bass -11 treble -1 \
		vol 14dB vol "$volume" \
		repeat "$(expr $time - 1)"
	"$(_radish_player)" \
		-c 2 --null synth "$time" "${noise_type}noise" \
		band -n "$noise_center" 499 \
		tremolo "$wave" 43 reverb 19 \
		bass -11 treble -1 \
		vol 14dB vol "$volume" \
		repeat "$(expr $time - 1)" >"$RADISH_STATUS_FILE" 2>&1 &
	echo $! >"$RADISH_PID_FILE"
}

main "$@"