· 6 years ago · Jun 02, 2019, 09:00 PM
1#! /bin/bash
2
3if [ -z "$BASH_VERSION" ]; then
4 echo 'Please use bash to run this script' >&2
5 exit 1
6elif [ "${BASH_VERSION%%.*}" -lt 4 ]; then
7 echo 'Please update bash to version 4' >&2
8 exit 1
9fi
10
11set -o posix
12
13trap song_list WINCH
14trap leave INT
15
16function flush
17{
18 while read -s -t 0.1; do
19 :
20 done
21}
22
23function leave
24{
25 stty $stty_orig
26 tput ed
27 tput el1
28 tput cnorm
29 tput rc
30 echo "shutdown" | nc localhost 11337 &> /dev/null
31 exit "${1:-0}"
32}
33
34function parse
35{
36 declare -g "$1=$(sed -n "s/$1[[:space:]]*=[[:space:]]*\(.*\)/\1/p" "$2")"
37}
38
39function json_parse
40{
41 if [ "$3" = "stream" ]; then
42 json=".collection[0].origin.$2"
43 echo "$1" | jq -r "$json"
44 elif [ "$3" = "toplevel" ]; then
45 echo "$1" | jq -r "$2"
46 else
47 json=".collection[0].$2"
48 echo "$1" | jq -r "$json"
49 fi
50}
51
52# We don't need OAuth info everywhere
53function getaddress {
54 address=$new_address
55 if [ "$playlist" = "stream" ]; then
56 address="$address&oauth_token=$oauth_token"
57 fi
58}
59
60function print_shortcuts
61{
62 tput ed
63 tput el1
64 tput rc
65 cat <<'EOF'
66h -> this help
67n -> next
68p -> previous
69r -> repeat current
70space -> pause/resume
71+ -> volume +2%
72- -> volume -2%
73e -> exit
74
75EOF
76
77 tput sc
78}
79
80function update_read
81{
82 if [ "$(playerctl --player=vlc status)" = "Playing" ]; then
83 ((duration[total] -= SECONDS))
84 SECONDS=0
85 fi
86 handle_input
87}
88
89function stop_song
90{
91 tput rc
92 local signal=KILL
93 playerctl --player=vlc stop &> /dev/null
94 echo "shutdown" | nc localhost 11337 &> /dev/null
95 kill -s "$signal" "$progress_bar_pid"
96 wait "$progress_bar_pid" &> /dev/null
97}
98
99function format_time
100{
101 printf '%s' "$(($1 / 60)):$(($1 % 60 / 10))$(($1 % 60 % 10))"
102}
103
104function song_list
105{
106 tput clear
107 sp=$(( total-($(tput lines)/2) ))
108 if [[ $sp -lt 0 ]]; then sp=1; fi;
109 for (( i=sp; i<total; i++)); do
110 tracktitle=${track_title[$i]}
111 titlelen=$(( $(tput cols) - ${#i} - 3 ))
112 if [[ $titlelen -lt ${#tracktitle} ]]; then
113 tracktitle="${tracktitle:0:$((titlelen-3))}..."
114 fi
115 tracktitle="$i: $tracktitle"
116 tput cup $(( i-sp )) 0
117 printf "%s" "$tracktitle"
118 done
119 songlisturl=$(echo "$(json_parse "$request" ".next_href" "toplevel")" | sed -e "s/limit=1/limit=$(( $(tput lines)-total+sp-1 ))/g" | sed -e "s/page_size=1/page_size=$(( $(tput lines)-total+sp-1 ))/g")
120 if [ "$playlist" = "stream" ]; then
121 songlisturl="$songlisturl&oauth_token=$oauth_token"
122 fi
123 songs=$(curl $songlisturl 2> /dev/null )
124 for (( i=total+1; i<$(tput lines)+sp; i++)); do
125 if [[ "$playlist" = "stream" ]]; then
126 listtitle="$(json_parse "$songs" ".collection[$((i-total-1))].origin.title" "toplevel")"
127 else
128 listtitle="$(json_parse "$songs" ".collection[$((i-total-1))].title" "toplevel")"
129 fi
130 if [[ "$listtitle" != *'-'* ]]; then
131 if [[ "$playlist" = "stream" ]]; then
132 artist="$(json_parse "$songs" ".collection[$((i-total-1))].origin.user.username" "toplevel")"
133 else
134 artist="$(json_parse "$songs" ".collection[$((i-total-1))].user.username" "toplevel")"
135 fi
136 listtitle="$artist - $listtitle"
137 fi
138 titlelen=$(( $(tput cols) - ${#i} - 3 ))
139 if [[ $titlelen -lt ${#listtitle} ]]; then
140 listtitle="${listtitle:0:$((titlelen-3))}..."
141 fi
142
143 tput cup $(( i-sp )) 0
144 printf "%s: %s" "$i" "$listtitle"
145 done
146}
147
148function progress_bar
149{
150 song_list
151 local cur_duration=0
152 local total_duration="$(format_time "${duration[total]}")"
153 while [ "$(playerctl --player=vlc status)" != "" ]; do
154 if [ "$(playerctl --player=vlc status)" = "Playing" ]; then
155 local time_disp="[$(format_time "$cur_duration") / $total_duration]"
156 tracktitle=${track_title[$total]}
157 titlelen=$(( $(tput cols) - ${#total} - ${#time_disp} - 3 ))
158 if [[ $titlelen -lt ${#tracktitle} ]]; then
159 tracktitle="${tracktitle:0:$((titlelen-3))}..."
160 fi
161 tracktitle="$total: $tracktitle $time_disp >"
162 sp=$(( total-($(tput lines)/2) ))
163 if [[ $sp -lt 0 ]]; then sp=1; fi;
164 tput rc
165 tput cup $(( total-sp )) 0
166 echo -n "$tracktitle"
167 (( cur_duration="$(playerctl --player=vlc position|cut -d'.' -f1)" ))
168 sleep 0.1
169 else
170 tracktitle=${track_title[$total]}
171 titlelen=$(( $(tput cols) - ${#total} - ${#time_disp} - 3 ))
172 if [[ $titlelen -lt ${#tracktitle} ]]; then
173 tracktitle="${tracktitle:0:$((titlelen-5))}..."
174 fi
175 tracktitle="$total: $tracktitle $time_disp ="
176 sp=$(( total-($(tput lines)/2) ))
177 if [[ $sp -lt 0 ]]; then sp=1; fi;
178 tput rc
179 tput cup $(( total-sp )) 0
180 echo -n "$tracktitle"
181 sleep 0.1
182 fi
183 done
184}
185
186function handle_input
187{
188 local IFS=
189
190 [ "${duration[$total]}" -le 0 ] && return
191
192 [ "$(playerctl --player=vlc status 2> /dev/null)" = "" ] && return
193
194 flush
195
196 read -s -n 1 -t "${duration[$total]}" key_input &> /dev/null
197
198 case "$key_input" in
199 '>')
200 stop_song
201 ((offset+=10))
202 continue
203 ;;
204 '<')
205 offset=1 # FIXME might screw up with pagination
206 stop_song
207 (( offset -= 10 ))
208 while true; do
209 [ "$total" -gt 0 ] && ((--total))
210 new_address="${addresses[total]}"
211 [ -n "$new_address" ] && break
212 done
213 continue
214 ;;
215 '.')
216 stop_song
217 continue
218 ;;
219 ',')
220 offset=1 # FIXME might screw up with pagination
221 stop_song
222 (( --total ))
223 while true; do
224 [ "$total" -gt 0 ] && ((--total))
225 new_address="${addresses[total]}"
226 [ -n "$new_address" ] && break
227 done
228 continue
229 ;;
230 $'\x1b')
231 read -rsn1 -t 0.1 tmp
232 if [[ "$tmp" == "[" ]]; then
233 read -rsn1 -t 0.1 tmp
234 case "$tmp" in
235 "A") #UP
236 update_read
237 continue
238 ;;
239 "B") #DOWN
240 update_read
241 continue
242 ;;
243 "C") #RIGHT
244 playerctl --player=vlc position 5+
245 (( cur_duration+=5 ))
246 update_read
247 continue
248 ;;
249 "D") #LEFT
250 playerctl --player=vlc position 5-
251 (( cur_duration-=5 ))
252 update_read
253 continue
254 ;;
255 esac
256 fi
257 read -rsn5 -t 0.1
258 continue
259 ;;
260 'r')
261 stop_song
262 ((--total))
263 continue
264 ;;
265 ' ')
266 playerctl --player=vlc play-pause
267 update_read
268 continue
269 ;;
270 '+')
271 playerctl volume "0.05+" --player=vlc
272 update_read
273 continue
274 ;;
275 '-')
276 playerctl volume "0.05-" --player=vlc
277 update_read
278 continue
279 ;;
280 'h')
281 print_shortcuts
282 update_read
283 ;;
284 'e')
285 stop_song
286 leave
287 ;;
288 *)
289 update_read
290 ;;
291 esac
292 tput ed
293 tput rc
294}
295
296# start
297
298missing=
299for cmd in curl vlc jq; do
300 command -v "$cmd" > /dev/null || missing+="$cmd, "
301done
302
303if [ -n "$missing" ]; then
304 printf 'Please install the following dependencies: %s\n' "${missing%, }"
305 exit 2
306fi
307
308readonly cfg_file=~/'.sc-stream.cfg'
309
310if [ ! -f "$cfg_file" ]; then
311 printf 'Username: '
312 read username
313 printf 'Client id: '
314 read client_id
315 printf 'OAuth token: '
316 read oauth_token
317 printf 'username=%s\nclient_id=%soauth_token=%s\n' "$username" "$client_id" "$oauth_token" > "$cfg_file"
318 echo
319fi
320
321parse client_id "$cfg_file"
322parse username "$cfg_file"
323parse oauth_token "$cfg_file"
324offset=1
325
326while [ "$#" -gt 0 ]; do
327 key="$1"
328
329 case "$key" in
330 -o|--offset)
331 offset="$2"
332 shift
333 ;;
334 -p|--playlist)
335 playlist="$2"
336 shift
337 ;;
338 -a|--artist)
339 username="$2"
340 : "${playlist:=tracks}"
341 shift
342 ;;
343 *)
344 cat <<'EOF'
345
346Usage: souncloud-stream [options]
347
348-o, --offset Begin playlist with an offset
349-a, --artist Listen to an artist tracks
350-p, --playlist Specify tracks or favorites
351
352
353Press h while playing to display shortcuts
354
355EOF
356 exit 2
357 ;;
358 esac
359 shift
360done
361
362: "${playlist:=favorites}"
363
364seconds=0
365total=0
366
367if [ "$playlist" = "stream" ]; then
368 readonly init_address="http://api.soundcloud.com/me/activities?client_id=$client_id&limit=1&linked_partitioning=1&oauth_token=$oauth_token"
369else
370 readonly init_address="http://api.soundcloud.com/users/$username/$playlist?client_id=$client_id&limit=1&linked_partitioning=1"
371fi
372address="$init_address"
373
374readonly error="$(printf '\033[0;31mError\033[0m')"
375
376tput civis
377tput sc
378
379stty_orig=`stty -g`
380stty -echo
381
382while true; do
383 request="$(curl -s "$address")"
384 new_address="$(json_parse "$request" ".next_href" "toplevel")"
385 addresses[total]="$address"
386
387 ((++total))
388
389 if [ -z "$new_address" ]; then
390 printf '%s: fatal no next address -> stopping\n' "$error"
391 leave 1
392 fi
393
394 track_title[total]="$(json_parse "$request" "title" "$playlist")"
395 if [[ "${track_title[$total]}" != *'-'* ]]; then
396 artist="$(json_parse "$request" "user.username" "$playlist")"
397 track_title[total]="$artist - ${track_title[$total]}"
398 fi
399
400 if [ "$total" -lt "$offset" ]; then
401 getaddress
402 continue
403 fi
404
405 streamable="$(json_parse "$request" "streamable" "$playlist")"
406 stream_address="$(json_parse "$request" "stream_url" "$playlist")"
407
408 if [ "$streamable" != true ] || [ -z "$stream_address" ]; then
409 printf '%s: song not streamable -> skipping\n' "$error" >&2
410 getaddress
411 addresses[total - 1]=
412 sleep 0.5
413 continue
414 fi
415
416 duration[total]="$(($(json_parse "$request" "duration" "$playlist") / 1000))"
417 request_stream="$(curl -s "$stream_address?client_id=$client_id")"
418 stream="$(json_parse "$request_stream" ".location" "toplevel")"
419 cvlc -d --play-and-exit --no-repeat --no-loop -q "$stream" -I rc --rc-host localhost:11337 --rc-fake-tty &> /dev/null &
420 vlc_pid="$!"
421 while [ $(playerctl --player=vlc status) = ""]; do
422 sleep 0.1
423 done
424 progress_bar &
425 progress_bar_pid="$!"
426 handle_input
427 getaddress
428 #kill $progress_bar_pid
429done