|
| 1 | +#!/bin/bash |
| 2 | + |
| 3 | +# |
| 4 | +# This is sp, the command-line Spotify controller. It talks to a running |
| 5 | +# instance of the Spotify Linux client over dbus, providing an interface not |
| 6 | +# unlike mpc. |
| 7 | +# |
| 8 | +# Put differently, it allows you to control Spotify without leaving the comfort |
| 9 | +# of your command line, and without a custom client or Premium subscription. |
| 10 | +# |
| 11 | +# As an added bonus, it also works with ssh, at and cron. |
| 12 | +# |
| 13 | +# Example: |
| 14 | +# $ sp weather girls raining men |
| 15 | +# $ sp current |
| 16 | +# Album 100 Hits Of The '80s |
| 17 | +# Artist The Weather Girls |
| 18 | +# Title It's Raining Men |
| 19 | +# $ sp pause |
| 20 | +# |
| 21 | +# Alarm clock example: |
| 22 | +# $ at 7:45 <<< 'sp bangarang' |
| 23 | +# |
| 24 | +# Remote example: |
| 25 | +# $ ssh [email protected] 'sp imperial march' |
| 26 | +# |
| 27 | +# |
| 28 | +# Copyright (C) 2013 Wander Nauta |
| 29 | +# |
| 30 | +# Permission is hereby granted, free of charge, to any person obtaining a copy |
| 31 | +# of this software, to deal in the Software without restriction, including |
| 32 | +# without limitation the rights to use, copy, modify, merge, publish, |
| 33 | +# distribute, sublicense, and/or sell copies of the Software, and to permit |
| 34 | +# persons to whom the Software is furnished to do so, subject to the following |
| 35 | +# conditions: |
| 36 | +# |
| 37 | +# The above copyright notice and this permission notice shall be included in |
| 38 | +# all copies or substantial portions of the Software. |
| 39 | +# |
| 40 | +# The software is provided "as is", without warranty of any kind, express or |
| 41 | +# implied, including but not limited to the warranties of merchantability, |
| 42 | +# fitness for a particular purpose and noninfringement. In no event shall the |
| 43 | +# authors or copyright holders be liable for any claim, damages or other |
| 44 | +# liability, whether in an action of contract, tort or otherwise, arising from, |
| 45 | +# out of or in connection with the software or the use or other dealings in the |
| 46 | +# software. |
| 47 | +# |
| 48 | + |
| 49 | +# CONSTANTS |
| 50 | + |
| 51 | +SP_VERSION="0.1" |
| 52 | +SP_DEST="org.mpris.MediaPlayer2.spotify" |
| 53 | +SP_PATH="/org/mpris/MediaPlayer2" |
| 54 | +SP_MEMB="org.mpris.MediaPlayer2.Player" |
| 55 | + |
| 56 | +# To get SP_ID and SP_SECRET register at |
| 57 | +# https://beta.developer.spotify.com/documentation/general/guides/app-settings/ |
| 58 | +SP_ID="<yout id>" |
| 59 | +SP_SECRET="<your secret>" |
| 60 | + |
| 61 | +# SHELL OPTIONS |
| 62 | + |
| 63 | +shopt -s expand_aliases |
| 64 | +SP_B64ID=$(echo -n "$SP_ID:$SP_SECRET"|base64) |
| 65 | +SP_B64ID=$(echo $SP_B64ID | sed 's/ //g') |
| 66 | + |
| 67 | +# UTILITY FUNCTIONS |
| 68 | + |
| 69 | +function require { |
| 70 | + hash $1 2>/dev/null || { |
| 71 | + echo >&2 "Error: '$1' is required, but was not found."; exit 1; |
| 72 | + } |
| 73 | +} |
| 74 | + |
| 75 | +# COMMON REQUIRED BINARIES |
| 76 | + |
| 77 | +# We need dbus-send to talk to Spotify. |
| 78 | +require dbus-send |
| 79 | + |
| 80 | +# Assert standard Unix utilities are available. |
| 81 | +require grep |
| 82 | +require sed |
| 83 | +require cut |
| 84 | +require tr |
| 85 | + |
| 86 | +# 'SPECIAL' (NON-DBUS-ALIAS) COMMANDS |
| 87 | + |
| 88 | +function sp-dbus { |
| 89 | + # Sends the given method to Spotify over dbus. |
| 90 | + dbus-send --print-reply --dest=$SP_DEST $SP_PATH $SP_MEMB.$1 ${*:2} > /dev/null |
| 91 | +} |
| 92 | + |
| 93 | +function sp-open { |
| 94 | + # Opens the given spotify: URI in Spotify. |
| 95 | + sp-dbus OpenUri string:$1 |
| 96 | +} |
| 97 | + |
| 98 | +function sp-metadata { |
| 99 | + # Prints the currently playing track in a parseable format. |
| 100 | + |
| 101 | + dbus-send \ |
| 102 | + --print-reply `# We need the reply.` \ |
| 103 | + --dest=$SP_DEST \ |
| 104 | + $SP_PATH \ |
| 105 | + org.freedesktop.DBus.Properties.Get \ |
| 106 | + string:"$SP_MEMB" string:'Metadata' \ |
| 107 | + | grep -Ev "^method" `# Ignore the first line.` \ |
| 108 | + | grep -Eo '("(.*)")|(\b[0-9][a-zA-Z0-9.]*\b)' `# Filter interesting fiels.`\ |
| 109 | + | sed -E '2~2 a|' `# Mark odd fields.` \ |
| 110 | + | tr -d '\n' `# Remove all newlines.` \ |
| 111 | + | sed -E 's/\|/\n/g' `# Restore newlines.` \ |
| 112 | + | sed -E 's/(xesam:)|(mpris:)//' `# Remove ns prefixes.` \ |
| 113 | + | sed -E 's/^"//' `# Strip leading...` \ |
| 114 | + | sed -E 's/"$//' `# ...and trailing quotes.` \ |
| 115 | + | sed -E 's/"+/|/' `# Regard "" as seperator.` \ |
| 116 | + | sed -E 's/ +/ /g' `# Merge consecutive spaces.` |
| 117 | +} |
| 118 | + |
| 119 | +function sp-current { |
| 120 | + # Prints the currently playing track in a friendly format. |
| 121 | + require column |
| 122 | + |
| 123 | + sp-metadata \ |
| 124 | + | grep --color=never -E "(title)|(album)|(artist)" \ |
| 125 | + | sed 's/^\(.\)/\U\1/' \ |
| 126 | + | column -t -s'|' |
| 127 | +} |
| 128 | + |
| 129 | +function sp-current-oneline { |
| 130 | + sp-metadata | grep -E "(title|artist)" | sed 's/^\(.\)*|//' | sed ':a;N;$!ba;s/\n/ | /g' |
| 131 | +} |
| 132 | + |
| 133 | +function sp-status { |
| 134 | + dbus-send \ |
| 135 | + --print-reply \ |
| 136 | + --dest=$SP_DEST \ |
| 137 | + $SP_PATH \ |
| 138 | + org.freedesktop.DBus.Properties.Get \ |
| 139 | + string:"$SP_MEMB" string:'PlaybackStatus' \ |
| 140 | + | tail -1 \ |
| 141 | + | cut -d "\"" -f2 |
| 142 | +} |
| 143 | + |
| 144 | +function sp-eval { |
| 145 | + # Prints the currently playing track as shell variables, ready to be eval'ed |
| 146 | + require sort |
| 147 | + |
| 148 | + sp-metadata \ |
| 149 | + | grep --color=never -E "(title)|(album)|(artist)|(trackid)|(trackNumber)" \ |
| 150 | + | sort -r \ |
| 151 | + | sed 's/^\([^|]*\)\|/\U\1/' \ |
| 152 | + | sed -E 's/\|/="/' \ |
| 153 | + | sed -E 's/$/"/' \ |
| 154 | + | sed -E 's/^/SPOTIFY_/' |
| 155 | +} |
| 156 | + |
| 157 | +function sp-art { |
| 158 | + # Prints the artUrl. |
| 159 | + |
| 160 | + sp-metadata | grep "artUrl" | cut -d'|' -f2 |
| 161 | +} |
| 162 | + |
| 163 | +function sp-display { |
| 164 | + # Calls display on the artUrl. |
| 165 | + |
| 166 | + require display |
| 167 | + display $(sp-art) |
| 168 | +} |
| 169 | + |
| 170 | +function sp-feh { |
| 171 | + # Calls feh on the artURl. |
| 172 | + |
| 173 | + require feh |
| 174 | + feh $(sp-art) |
| 175 | +} |
| 176 | + |
| 177 | +function sp-url { |
| 178 | + # Prints the HTTP url. |
| 179 | + |
| 180 | + TRACK=$(sp-metadata | grep "url" | cut -d'|' -f2 | cut -d':' -f3) |
| 181 | + echo "http://open.spotify.com/track/$TRACK" |
| 182 | +} |
| 183 | + |
| 184 | +function sp-clip { |
| 185 | + # Copies the HTTP url. |
| 186 | + |
| 187 | + require xclip |
| 188 | + sp-url | xclip |
| 189 | +} |
| 190 | + |
| 191 | +function sp-http { |
| 192 | + # xdg-opens the HTTP url. |
| 193 | + |
| 194 | + require xdg-open |
| 195 | + xdg-open $(sp-url) |
| 196 | +} |
| 197 | + |
| 198 | +function sp-help { |
| 199 | + # Prints usage information. |
| 200 | + |
| 201 | + echo "Usage: sp [command]" |
| 202 | + echo "Control a running Spotify instance from the command line." |
| 203 | + echo "" |
| 204 | + echo " sp play - Play/pause Spotify" |
| 205 | + echo " sp pause - Pause Spotify" |
| 206 | + echo " sp next - Go to next track" |
| 207 | + echo " sp prev - Go to previous track" |
| 208 | + echo "" |
| 209 | + echo " sp current - Format the currently playing track" |
| 210 | + echo " sp metadata - Dump the current track's metadata" |
| 211 | + echo " sp eval - Return the metadata as a shell script" |
| 212 | + echo "" |
| 213 | + echo " sp art - Print the URL to the current track's album artwork" |
| 214 | + echo " sp display - Display the current album artwork with \`display\`" |
| 215 | + echo " sp feh - Display the current album artwork with \`feh\`" |
| 216 | + echo "" |
| 217 | + echo " sp url - Print the HTTP URL for the currently playing track" |
| 218 | + echo " sp clip - Copy the HTTP URL to the X clipboard" |
| 219 | + echo " sp http - Open the HTTP URL in a web browser" |
| 220 | + echo "" |
| 221 | + echo " sp open <uri> - Open a spotify: uri" |
| 222 | + echo " sp search <q> - Start playing the best search result for the given query" |
| 223 | + echo "" |
| 224 | + echo " sp version - Show version information" |
| 225 | + echo " sp help - Show this information" |
| 226 | + echo "" |
| 227 | + echo "Any other argument will start a search (i.e. 'sp foo' will search for foo)." |
| 228 | +} |
| 229 | + |
| 230 | +function sp-search { |
| 231 | + # Searches for tracks, plays the first result. |
| 232 | + |
| 233 | + require curl |
| 234 | + #send request for token with ID and SecretID encoded to base64->grep take only token from reply->trim reply down to token-> modified request to include token in header |
| 235 | + Q="$@" |
| 236 | + ST=$(curl -H "Authorization: Basic $SP_B64ID" -d grant_type=client_credentials https://accounts.spotify.com/api/token --silent \ |
| 237 | + | grep -E -o "access_token\":\"[a-zA-Z0-9_-]+\"" -m 1 ) |
| 238 | + |
| 239 | + echo $Q |
| 240 | + |
| 241 | + ST2=${ST:15:115}} |
| 242 | + SPTFY_URI=$( \ |
| 243 | + curl -H "Authorization: Bearer $ST2" -s -G --data-urlencode "q=$Q" --data type=artist,track https://api.spotify.com/v1/search/ \ |
| 244 | + | grep -E -o "spotify:track:[a-zA-Z0-9]+" -m 1 \ |
| 245 | + ) |
| 246 | + |
| 247 | + sp-open $SPTFY_URI |
| 248 | +} |
| 249 | + |
| 250 | + |
| 251 | +#function sp-search { |
| 252 | +# # Searches for tracks, plays the first result. |
| 253 | +# |
| 254 | +# require curl |
| 255 | + |
| 256 | +#curl -s -G --data-urlencode "q=enjoy the silence" --data type=track "https://api.spotify.com/v1/search/" -H "Accept: application/json" -H "Authorization: Bearer BQCk-QAOp4r2hHf6bMY1B4WSfWnBnLXbYlLn7OJi0iGzR5JNg6uaMtNzokhCLTr3ETljtLb1KqhHGokfyLj9Fq3JkDz6yqwqM9NCa9RbGa7_ovJ0n9XZgev0bJ8r-N7hrF3TNN-PKY6YIKpOoWM" |
| 257 | +# |
| 258 | + # Q="$@" |
| 259 | + #SPTFY_URI=$( \ |
| 260 | +# curl -s -G --data-urlencode "q=$Q" --data type=artist,track https://api.spotify.com/v1/search/ -H "Accept: application/json" -H "Authorization: Bearer $USER_TOKEN" \ |
| 261 | +# | grep -E -o "spotify:track:[a-zA-Z0-9]+" -m 1 \ |
| 262 | +# ) |
| 263 | +# |
| 264 | +# sp-open $SPTFY_URI |
| 265 | +#} |
| 266 | + |
| 267 | +function sp-version { |
| 268 | + # Prints version information. |
| 269 | + |
| 270 | + echo "sp $SP_VERSION" |
| 271 | + echo "Copyright (C) 2013 Wander Nauta" |
| 272 | + echo "License MIT" |
| 273 | +} |
| 274 | + |
| 275 | +# 'SIMPLE' (DBUS-ALIAS) COMMANDS |
| 276 | + |
| 277 | +alias sp-play=" sp-dbus PlayPause" |
| 278 | +alias sp-pause=" sp-dbus Pause" |
| 279 | +alias sp-next=" sp-dbus Next" |
| 280 | +alias sp-prev=" sp-dbus Previous" |
| 281 | + |
| 282 | +# DISPATCHER |
| 283 | + |
| 284 | +# First, we connect to the dbus session spotify is on. This isn't really needed |
| 285 | +# when running locally, but is crucial when we don't have an X display handy |
| 286 | +# (for instance, when running sp over ssh.) |
| 287 | + |
| 288 | +SPOTIFY_PID="$(pidof -s spotify)" |
| 289 | + |
| 290 | +if [[ -z "$SPOTIFY_PID" ]]; then |
| 291 | + echo "Error: Spotify is not running." |
| 292 | + exit 1 |
| 293 | +fi |
| 294 | + |
| 295 | +QUERY_ENVIRON="$(cat /proc/${SPOTIFY_PID}/environ | tr '\0' '\n' | grep "DBUS_SESSION_BUS_ADDRESS" | cut -d "=" -f 2-)" |
| 296 | +if [[ "${QUERY_ENVIRON}" != "" ]]; then |
| 297 | + export DBUS_SESSION_BUS_ADDRESS="${QUERY_ENVIRON}" |
| 298 | +fi |
| 299 | + |
| 300 | +# Then we dispatch the command. |
| 301 | + |
| 302 | +subcommand="$1" |
| 303 | + |
| 304 | +if [[ -z "$subcommand" ]]; then |
| 305 | + # No arguments given, print help. |
| 306 | + sp-help |
| 307 | +else |
| 308 | + # Arguments given, check if it's a command. |
| 309 | + if $(type sp-$subcommand > /dev/null 2> /dev/null); then |
| 310 | + # It is. Run it. |
| 311 | + shift |
| 312 | + eval "sp-$subcommand $@" |
| 313 | + else |
| 314 | + # It's not. Try a search. |
| 315 | + eval "sp-search $@" |
| 316 | + fi |
| 317 | +fi |
0 commit comments