spotify
20106 bytes
1#!/usr/bin/env bash
2
3function spotify() {
4# Copyright (c) 2012--2023 Harish Narayanan <mail@harishnarayanan.org>
5#
6# Contains numerous helpful contributions from Jorge Colindres, Thomas
7# Pritchard, iLan Epstein, Gabriele Bonetti, Sean Heller, Eric Martin
8# and Peter Fonseca.
9
10# Permission is hereby granted, free of charge, to any person
11# obtaining a copy of this software and associated documentation files
12# (the "Software"), to deal in the Software without restriction,
13# including without limitation the rights to use, copy, modify, merge,
14# publish, distribute, sublicense, and/or sell copies of the Software,
15# and to permit persons to whom the Software is furnished to do so,
16# subject to the following conditions:
17
18# The above copyright notice and this permission notice shall be
19# included in all copies or substantial portions of the Software.
20
21# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
25# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
26# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
27# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28# SOFTWARE.
29
30USER_CONFIG_DEFAULTS="CLIENT_ID=\"\"\nCLIENT_SECRET=\"\"";
31USER_CONFIG_FILE="${HOME}/.shpotify.cfg";
32if ! [[ -f "${USER_CONFIG_FILE}" ]]; then
33 touch "${USER_CONFIG_FILE}";
34 echo -e "${USER_CONFIG_DEFAULTS}" > "${USER_CONFIG_FILE}";
35fi
36source "${USER_CONFIG_FILE}";
37
38# Set the percent change in volume for vol up and vol down
39VOL_INCREMENT=10
40
41showAPIHelp() {
42 echo;
43 echo "Connecting to Spotify's API:";
44 echo;
45 echo " This command line application needs to connect to Spotify's API in order to";
46 echo " find music by name. It is very likely you want this feature!";
47 echo;
48 echo " To get this to work, you need to sign up (or in) and create an 'Application' at:";
49 echo " https://developer.spotify.com/dashboard/create";
50 echo;
51 echo " Once you've created an application, find the 'Client ID' and 'Client Secret'";
52 echo " values, and enter them into your shpotify config file at '${USER_CONFIG_FILE}'";
53 echo;
54 echo " Be sure to quote your values and don't add any extra spaces!";
55 echo " When done, it should look like this (but with your own values):";
56 echo ' CLIENT_ID="abc01de2fghijk345lmnop"';
57 echo ' CLIENT_SECRET="qr6stu789vwxyz"';
58}
59
60showHelp () {
61 echo "Usage:";
62 echo;
63 echo " `basename $0` <command>";
64 echo;
65 echo "Commands:";
66 echo;
67 echo " play # Resumes playback where Spotify last left off.";
68 echo " play <song name> # Finds a song by name and plays it.";
69 echo " play album <album name> # Finds an album by name and plays it.";
70 echo " play artist <artist name> # Finds an artist by name and plays it.";
71 echo " play list <playlist name> # Finds a playlist by name and plays it.";
72 echo " play uri <uri> # Play songs from specific uri.";
73 echo;
74 echo " next # Skips to the next song in a playlist.";
75 echo " prev # Returns to the previous song in a playlist.";
76 echo " replay # Replays the current track from the beginning.";
77 echo " pos <time> # Jumps to a time (in secs) in the current song.";
78 echo " pause # Pauses (or resumes) Spotify playback.";
79 echo " stop # Stops playback.";
80 echo " quit # Stops playback and quits Spotify.";
81 echo;
82 echo " vol up # Increases the volume by 10%.";
83 echo " vol down # Decreases the volume by 10%.";
84 echo " vol <amount> # Sets the volume to an amount between 0 and 100.";
85 echo " vol [show] # Shows the current Spotify volume.";
86 echo;
87 echo " status # Shows the current player status.";
88 echo " status artist # Shows the currently playing artist.";
89 echo " status album # Shows the currently playing album.";
90 echo " status track # Shows the currently playing track.";
91 echo;
92 echo " share # Displays the current song's Spotify URL and URI."
93 echo " share url # Displays the current song's Spotify URL and copies it to the clipboard."
94 echo " share uri # Displays the current song's Spotify URI and copies it to the clipboard."
95 echo;
96 echo " toggle shuffle # Toggles shuffle playback mode.";
97 echo " toggle repeat # Toggles repeat playback mode.";
98 showAPIHelp
99}
100
101cecho(){
102 bold=$(tput bold);
103 green=$(tput setaf 2);
104 reset=$(tput sgr0);
105 echo $bold$green"$1"$reset;
106}
107
108showArtist() {
109 echo `osascript -e 'tell application "Spotify" to artist of current track as string'`;
110}
111
112showAlbum() {
113 echo `osascript -e 'tell application "Spotify" to album of current track as string'`;
114}
115
116showTrack() {
117 echo `osascript -e 'tell application "Spotify" to name of current track as string'`;
118}
119
120showStatus () {
121 state=`osascript -e 'tell application "Spotify" to player state as string'`;
122 cecho "Spotify is currently $state.";
123 duration=`osascript -e 'tell application "Spotify"
124 set durSec to (duration of current track / 1000) as text
125 set tM to (round (durSec / 60) rounding down) as text
126 if length of ((durSec mod 60 div 1) as text) is greater than 1 then
127 set tS to (durSec mod 60 div 1) as text
128 else
129 set tS to ("0" & (durSec mod 60 div 1)) as text
130 end if
131 set myTime to tM as text & ":" & tS as text
132 end tell
133 return myTime'`;
134 position=`osascript -e 'tell application "Spotify"
135 set pos to player position
136 set nM to (round (pos / 60) rounding down) as text
137 if length of ((round (pos mod 60) rounding down) as text) is greater than 1 then
138 set nS to (round (pos mod 60) rounding down) as text
139 else
140 set nS to ("0" & (round (pos mod 60) rounding down)) as text
141 end if
142 set nowAt to nM as text & ":" & nS as text
143 end tell
144 return nowAt'`;
145
146 echo -e $reset"Artist: $(showArtist)\nAlbum: $(showAlbum)\nTrack: $(showTrack) \nPosition: $position / $duration";
147}
148
149if [ $# = 0 ]; then
150 showHelp;
151else
152 if [ ! -d /Applications/Spotify.app ] && [ ! -d $HOME/Applications/Spotify.app ]; then
153 echo "The Spotify application must be installed."
154 return 1
155 fi
156
157 if [ $(osascript -e 'application "Spotify" is running') = "false" ]; then
158 osascript -e 'tell application "Spotify" to activate' || return 1
159 sleep 2
160 fi
161fi
162while [ $# -gt 0 ]; do
163 arg=$1;
164
165 case $arg in
166 "play" )
167 if [ $# != 1 ]; then
168 # There are additional arguments, so find out how many
169 array=( $@ );
170 len=${#array[@]};
171 SPOTIFY_SEARCH_API="https://api.spotify.com/v1/search";
172 SPOTIFY_TOKEN_URI="https://accounts.spotify.com/api/token";
173 if [ -z "${CLIENT_ID}" ]; then
174 cecho "Invalid Client ID, please update ${USER_CONFIG_FILE}";
175 showAPIHelp;
176 return 1;
177 fi
178 if [ -z "${CLIENT_SECRET}" ]; then
179 cecho "Invalid Client Secret, please update ${USER_CONFIG_FILE}";
180 showAPIHelp;
181 return 1;
182 fi
183 SHPOTIFY_CREDENTIALS=$(printf "${CLIENT_ID}:${CLIENT_SECRET}" | base64 | tr -d "\n"|tr -d '\r');
184 SPOTIFY_PLAY_URI="";
185
186 getAccessToken() {
187 cecho "Connecting to Spotify's API";
188
189 SPOTIFY_TOKEN_RESPONSE_DATA=$( \
190 curl "${SPOTIFY_TOKEN_URI}" \
191 --silent \
192 -X "POST" \
193 -H "Authorization: Basic ${SHPOTIFY_CREDENTIALS}" \
194 -d "grant_type=client_credentials" \
195 )
196 if ! [[ "${SPOTIFY_TOKEN_RESPONSE_DATA}" =~ "access_token" ]]; then
197 cecho "Authorization failed, please check ${USER_CONFG_FILE}"
198 cecho "${SPOTIFY_TOKEN_RESPONSE_DATA}"
199 showAPIHelp
200 return 1
201 fi
202 SPOTIFY_ACCESS_TOKEN=$( \
203 printf "${SPOTIFY_TOKEN_RESPONSE_DATA}" \
204 | command grep -E -o '"access_token":".*",' \
205 | sed 's/"access_token"://g' \
206 | sed 's/"//g' \
207 | sed 's/,.*//g' \
208 )
209 }
210
211 searchAndPlay() {
212 type="$1"
213 Q="$2"
214
215 getAccessToken;
216
217 cecho "Searching ${type}s for: $Q";
218
219 SPOTIFY_PLAY_URI=$( \
220 curl -s -G $SPOTIFY_SEARCH_API \
221 -H "Authorization: Bearer ${SPOTIFY_ACCESS_TOKEN}" \
222 -H "Accept: application/json" \
223 --data-urlencode "q=$Q" \
224 -d "type=$type&limit=1&offset=0" \
225 | command grep -E -o "spotify:$type:[a-zA-Z0-9]+" -m 1
226 )
227 }
228
229 case $2 in
230 "list" )
231 _args=${array[@]:2:$len};
232 Q=$_args;
233
234 getAccessToken;
235
236 cecho "Searching playlists for: $Q";
237
238 results=$( \
239 curl -s -G $SPOTIFY_SEARCH_API --data-urlencode "q=$Q" -d "type=playlist&limit=10&offset=0" -H "Accept: application/json" -H "Authorization: Bearer ${SPOTIFY_ACCESS_TOKEN}" \
240 | command grep -E -o "spotify:playlist:[a-zA-Z0-9]+" -m 10 \
241 )
242
243 count=$( \
244 echo "$results" | command grep -c "spotify:playlist" \
245 )
246
247 if [ "$count" -gt 0 ]; then
248 random=$(( $RANDOM % $count));
249
250 SPOTIFY_PLAY_URI=$( \
251 echo "$results" | awk -v random="$random" '/spotify:playlist:[a-zA-Z0-9]+/{i++}i==random{print; exit}' \
252 )
253 fi;;
254
255 "album" | "artist" | "track" )
256 _args=${array[@]:2:$len};
257 searchAndPlay $2 "$_args";;
258
259 "uri" )
260 SPOTIFY_PLAY_URI=${array[@]:2:$len};;
261
262 * )
263 _args=${array[@]:1:$len};
264 searchAndPlay track "$_args";;
265 esac
266
267 if [ "$SPOTIFY_PLAY_URI" != "" ]; then
268 if [ "$2" = "uri" ]; then
269 cecho "Playing Spotify URI: $SPOTIFY_PLAY_URI";
270 else
271 cecho "Playing ($Q Search) -> Spotify URI: $SPOTIFY_PLAY_URI";
272 fi
273
274 osascript -e "tell application \"Spotify\" to play track \"$SPOTIFY_PLAY_URI\"";
275
276 else
277 cecho "No results when searching for $Q";
278 fi
279
280 else
281
282 # play is the only param
283 cecho "Playing Spotify.";
284 osascript -e 'tell application "Spotify" to play';
285 fi
286 break ;;
287
288 "pause" )
289 state=`osascript -e 'tell application "Spotify" to player state as string'`;
290 if [ $state = "playing" ]; then
291 cecho "Pausing Spotify.";
292 else
293 cecho "Playing Spotify.";
294 fi
295
296 osascript -e 'tell application "Spotify" to playpause';
297 break ;;
298
299 "stop" )
300 state=`osascript -e 'tell application "Spotify" to player state as string'`;
301 if [ $state = "playing" ]; then
302 cecho "Pausing Spotify.";
303 osascript -e 'tell application "Spotify" to playpause';
304 else
305 cecho "Spotify is already stopped."
306 fi
307
308 break ;;
309
310 "quit" ) cecho "Quitting Spotify.";
311 osascript -e 'tell application "Spotify" to quit';
312 break ;;
313
314 "next" ) cecho "Going to next track." ;
315 osascript -e 'tell application "Spotify" to next track';
316 showStatus;
317 break ;;
318
319 "prev" ) cecho "Going to previous track.";
320 osascript -e '
321 tell application "Spotify"
322 set player position to 0
323 previous track
324 end tell';
325 showStatus;
326 break ;;
327
328 "replay" ) cecho "Replaying current track.";
329 osascript -e 'tell application "Spotify" to set player position to 0'
330 break ;;
331
332 "vol" )
333 vol=`osascript -e 'tell application "Spotify" to sound volume as integer'`;
334 if [[ $2 = "" || $2 = "show" ]]; then
335 cecho "Current Spotify volume level is $vol.";
336 break ;
337 elif [ "$2" = "up" ]; then
338 if [ $vol -le $(( 100-$VOL_INCREMENT )) ]; then
339 newvol=$(( vol+$VOL_INCREMENT ));
340 cecho "Increasing Spotify volume to $newvol.";
341 else
342 newvol=100;
343 cecho "Spotify volume level is at max.";
344 fi
345 elif [ "$2" = "down" ]; then
346 if [ $vol -ge $(( $VOL_INCREMENT )) ]; then
347 newvol=$(( vol-$VOL_INCREMENT ));
348 cecho "Reducing Spotify volume to $newvol.";
349 else
350 newvol=0;
351 cecho "Spotify volume level is at min.";
352 fi
353 elif [[ $2 =~ ^[0-9]+$ ]] && [[ $2 -ge 0 && $2 -le 100 ]]; then
354 newvol=$2;
355 cecho "Setting Spotify volume level to $newvol";
356 else
357 echo "Improper use of 'vol' command"
358 echo "The 'vol' command should be used as follows:"
359 echo " vol up # Increases the volume by $VOL_INCREMENT%.";
360 echo " vol down # Decreases the volume by $VOL_INCREMENT%.";
361 echo " vol [amount] # Sets the volume to an amount between 0 and 100.";
362 echo " vol # Shows the current Spotify volume.";
363 return 1;
364 fi
365
366 osascript -e "tell application \"Spotify\" to set sound volume to $newvol";
367 break ;;
368
369 "toggle" )
370 if [ "$2" = "shuffle" ]; then
371 osascript -e 'tell application "Spotify" to set shuffling to not shuffling';
372 curr=`osascript -e 'tell application "Spotify" to shuffling'`;
373 cecho "Spotify shuffling set to $curr";
374 elif [ "$2" = "repeat" ]; then
375 osascript -e 'tell application "Spotify" to set repeating to not repeating';
376 curr=`osascript -e 'tell application "Spotify" to repeating'`;
377 cecho "Spotify repeating set to $curr";
378 fi
379 break ;;
380
381 "status" )
382 if [ $# != 1 ]; then
383 # There are additional arguments, a status subcommand
384 case $2 in
385 "artist" )
386 showArtist;
387 break ;;
388
389 "album" )
390 showAlbum;
391 break ;;
392
393 "track" )
394 showTrack;
395 break ;;
396 esac
397 else
398 # status is the only param
399 showStatus;
400 fi
401 break ;;
402
403 "info" )
404 info=`osascript -e 'tell application "Spotify"
405 set durSec to (duration of current track / 1000)
406 set tM to (round (durSec / 60) rounding down) as text
407 if length of ((durSec mod 60 div 1) as text) is greater than 1 then
408 set tS to (durSec mod 60 div 1) as text
409 else
410 set tS to ("0" & (durSec mod 60 div 1)) as text
411 end if
412 set myTime to tM as text & "min " & tS as text & "s"
413 set pos to player position
414 set nM to (round (pos / 60) rounding down) as text
415 if length of ((round (pos mod 60) rounding down) as text) is greater than 1 then
416 set nS to (round (pos mod 60) rounding down) as text
417 else
418 set nS to ("0" & (round (pos mod 60) rounding down)) as text
419 end if
420 set nowAt to nM as text & "min " & nS as text & "s"
421 set info to "" & "\nArtist: " & artist of current track
422 set info to info & "\nTrack: " & name of current track
423 set info to info & "\nAlbum Artist: " & album artist of current track
424 set info to info & "\nAlbum: " & album of current track
425 set info to info & "\nSeconds: " & durSec
426 set info to info & "\nSeconds played: " & pos
427 set info to info & "\nDuration: " & mytime
428 set info to info & "\nNow at: " & nowAt
429 set info to info & "\nPlayed Count: " & played count of current track
430 set info to info & "\nTrack Number: " & track number of current track
431 set info to info & "\nPopularity: " & popularity of current track
432 set info to info & "\nId: " & id of current track
433 set info to info & "\nSpotify URL: " & spotify url of current track
434 set info to info & "\nArtwork: " & artwork url of current track
435 set info to info & "\nPlayer: " & player state
436 set info to info & "\nVolume: " & sound volume
437 set info to info & "\nShuffle: " & shuffling
438 set info to info & "\nRepeating: " & repeating
439 end tell
440 return info'`
441 cecho "$info";
442 break ;;
443
444 "share" )
445 uri=`osascript -e 'tell application "Spotify" to spotify url of current track'`;
446 remove='spotify:track:'
447 url=${uri#$remove}
448 url="https://open.spotify.com/track/$url"
449
450 if [ "$2" = "" ]; then
451 cecho "Spotify URL: $url"
452 cecho "Spotify URI: $uri"
453 echo "To copy the URL or URI to your clipboard, use:"
454 echo "\`spotify share url\` or"
455 echo "\`spotify share uri\` respectively."
456 elif [ "$2" = "url" ]; then
457 cecho "Spotify URL: $url";
458 echo -n $url | pbcopy
459 elif [ "$2" = "uri" ]; then
460 cecho "Spotify URI: $uri";
461 echo -n $uri | pbcopy
462 fi
463 break ;;
464
465 "pos" )
466 cecho "Adjusting Spotify play position."
467 osascript -e "tell application \"Spotify\" to set player position to $2";
468 break ;;
469
470 "help" )
471 showHelp;
472 break ;;
473 * )
474 showHelp;
475 return 1;
476
477 esac
478done
479}