Skip to content

Instantly share code, notes, and snippets.

@streetturtle
Forked from wandernauta/sp
Last active March 29, 2024 08:53
Show Gist options
  • Star 40 You must be signed in to star a gist
  • Fork 12 You must be signed in to fork a gist
  • Save streetturtle/fa6258f3ff7b17747ee3 to your computer and use it in GitHub Desktop.
Save streetturtle/fa6258f3ff7b17747ee3 to your computer and use it in GitHub Desktop.
sp is a command-line client for Spotify's dbus interface. Play, pause, skip and search tracks from the comfort of your command line.
#!/bin/bash
#
# This is sp, the command-line Spotify controller. It talks to a running
# instance of the Spotify Linux client over dbus, providing an interface not
# unlike mpc.
#
# Put differently, it allows you to control Spotify without leaving the comfort
# of your command line, and without a custom client or Premium subscription.
#
# As an added bonus, it also works with ssh, at and cron.
#
# Example:
# $ sp weather girls raining men
# $ sp current
# Album 100 Hits Of The '80s
# Artist The Weather Girls
# Title It's Raining Men
# $ sp pause
#
# Alarm clock example:
# $ at 7:45 <<< 'sp bangarang'
#
# Remote example:
# $ ssh vader@prod02.nomoon.ta 'sp imperial march'
#
#
# Copyright (C) 2013 Wander Nauta
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software, to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to permit
# persons to whom the Software is furnished to do so, subject to the following
# conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# The software is provided "as is", without warranty of any kind, express or
# implied, including but not limited to the warranties of merchantability,
# fitness for a particular purpose and noninfringement. In no event shall the
# authors or copyright holders be liable for any claim, damages or other
# liability, whether in an action of contract, tort or otherwise, arising from,
# out of or in connection with the software or the use or other dealings in the
# software.
#
# CONSTANTS
SP_VERSION="0.1"
SP_DEST="org.mpris.MediaPlayer2.spotify"
SP_PATH="/org/mpris/MediaPlayer2"
SP_MEMB="org.mpris.MediaPlayer2.Player"
# To get SP_ID and SP_SECRET register at
# https://beta.developer.spotify.com/documentation/general/guides/app-settings/
SP_ID="<yout id>"
SP_SECRET="<your secret>"
# SHELL OPTIONS
shopt -s expand_aliases
SP_B64ID=$(echo -n "$SP_ID:$SP_SECRET"|base64)
SP_B64ID=$(echo $SP_B64ID | sed 's/ //g')
# UTILITY FUNCTIONS
function require {
hash $1 2>/dev/null || {
echo >&2 "Error: '$1' is required, but was not found."; exit 1;
}
}
# COMMON REQUIRED BINARIES
# We need dbus-send to talk to Spotify.
require dbus-send
# Assert standard Unix utilities are available.
require grep
require sed
require cut
require tr
# 'SPECIAL' (NON-DBUS-ALIAS) COMMANDS
function sp-dbus {
# Sends the given method to Spotify over dbus.
dbus-send --print-reply --dest=$SP_DEST $SP_PATH $SP_MEMB.$1 ${*:2} > /dev/null
}
function sp-open {
# Opens the given spotify: URI in Spotify.
sp-dbus OpenUri string:$1
}
function sp-metadata {
# Prints the currently playing track in a parseable format.
dbus-send \
--print-reply `# We need the reply.` \
--dest=$SP_DEST \
$SP_PATH \
org.freedesktop.DBus.Properties.Get \
string:"$SP_MEMB" string:'Metadata' \
| grep -Ev "^method" `# Ignore the first line.` \
| grep -Eo '("(.*)")|(\b[0-9][a-zA-Z0-9.]*\b)' `# Filter interesting fiels.`\
| sed -E '2~2 a|' `# Mark odd fields.` \
| tr -d '\n' `# Remove all newlines.` \
| sed -E 's/\|/\n/g' `# Restore newlines.` \
| sed -E 's/(xesam:)|(mpris:)//' `# Remove ns prefixes.` \
| sed -E 's/^"//' `# Strip leading...` \
| sed -E 's/"$//' `# ...and trailing quotes.` \
| sed -E 's/"+/|/' `# Regard "" as seperator.` \
| sed -E 's/ +/ /g' `# Merge consecutive spaces.`
}
function sp-current {
# Prints the currently playing track in a friendly format.
require column
sp-metadata \
| grep --color=never -E "(title)|(album)|(artist)" \
| sed 's/^\(.\)/\U\1/' \
| column -t -s'|'
}
function sp-current-oneline {
sp-metadata | grep -E "(title|artist)" | sed 's/^\(.\)*|//' | sed ':a;N;$!ba;s/\n/ | /g'
}
function sp-status {
dbus-send \
--print-reply \
--dest=$SP_DEST \
$SP_PATH \
org.freedesktop.DBus.Properties.Get \
string:"$SP_MEMB" string:'PlaybackStatus' \
| tail -1 \
| cut -d "\"" -f2
}
function sp-eval {
# Prints the currently playing track as shell variables, ready to be eval'ed
require sort
sp-metadata \
| grep --color=never -E "(title)|(album)|(artist)|(trackid)|(trackNumber)" \
| sort -r \
| sed 's/^\([^|]*\)\|/\U\1/' \
| sed -E 's/\|/="/' \
| sed -E 's/$/"/' \
| sed -E 's/^/SPOTIFY_/'
}
function sp-art {
# Prints the artUrl.
sp-metadata | grep "artUrl" | cut -d'|' -f2
}
function sp-display {
# Calls display on the artUrl.
require display
display $(sp-art)
}
function sp-feh {
# Calls feh on the artURl.
require feh
feh $(sp-art)
}
function sp-url {
# Prints the HTTP url.
TRACK=$(sp-metadata | grep "url" | cut -d'|' -f2 | cut -d':' -f3)
echo "http://open.spotify.com/track/$TRACK"
}
function sp-clip {
# Copies the HTTP url.
require xclip
sp-url | xclip
}
function sp-http {
# xdg-opens the HTTP url.
require xdg-open
xdg-open $(sp-url)
}
function sp-help {
# Prints usage information.
echo "Usage: sp [command]"
echo "Control a running Spotify instance from the command line."
echo ""
echo " sp play - Play/pause Spotify"
echo " sp pause - Pause Spotify"
echo " sp next - Go to next track"
echo " sp prev - Go to previous track"
echo ""
echo " sp current - Format the currently playing track"
echo " sp metadata - Dump the current track's metadata"
echo " sp eval - Return the metadata as a shell script"
echo ""
echo " sp art - Print the URL to the current track's album artwork"
echo " sp display - Display the current album artwork with \`display\`"
echo " sp feh - Display the current album artwork with \`feh\`"
echo ""
echo " sp url - Print the HTTP URL for the currently playing track"
echo " sp clip - Copy the HTTP URL to the X clipboard"
echo " sp http - Open the HTTP URL in a web browser"
echo ""
echo " sp open <uri> - Open a spotify: uri"
echo " sp search <q> - Start playing the best search result for the given query"
echo ""
echo " sp version - Show version information"
echo " sp help - Show this information"
echo ""
echo "Any other argument will start a search (i.e. 'sp foo' will search for foo)."
}
function sp-search {
# Searches for tracks, plays the first result.
require curl
#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
Q="$@"
ST=$(curl -H "Authorization: Basic $SP_B64ID" -d grant_type=client_credentials https://accounts.spotify.com/api/token --silent \
| grep -E -o "access_token\":\"[a-zA-Z0-9_-]+\"" -m 1 )
echo $Q
ST2=${ST:15:86}}
SPTFY_URI=$( \
curl -H "Authorization: Bearer $ST2" -s -G --data-urlencode "q=$Q" --data type=artist,track https://api.spotify.com/v1/search/ \
| grep -E -o "spotify:track:[a-zA-Z0-9]+" -m 1 \
)
sp-open $SPTFY_URI
}
#function sp-search {
# # Searches for tracks, plays the first result.
#
# require curl
#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"
#
# Q="$@"
#SPTFY_URI=$( \
# 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" \
# | grep -E -o "spotify:track:[a-zA-Z0-9]+" -m 1 \
# )
#
# sp-open $SPTFY_URI
#}
function sp-version {
# Prints version information.
echo "sp $SP_VERSION"
echo "Copyright (C) 2013 Wander Nauta"
echo "License MIT"
}
# 'SIMPLE' (DBUS-ALIAS) COMMANDS
alias sp-play=" sp-dbus PlayPause"
alias sp-pause=" sp-dbus Pause"
alias sp-next=" sp-dbus Next"
alias sp-prev=" sp-dbus Previous"
# DISPATCHER
# First, we connect to the dbus session spotify is on. This isn't really needed
# when running locally, but is crucial when we don't have an X display handy
# (for instance, when running sp over ssh.)
SPOTIFY_PID="$(pidof -s spotify)"
if [[ -z "$SPOTIFY_PID" ]]; then
echo "Error: Spotify is not running."
exit 1
fi
QUERY_ENVIRON="$(cat /proc/${SPOTIFY_PID}/environ | tr '\0' '\n' | grep "DBUS_SESSION_BUS_ADDRESS" | cut -d "=" -f 2-)"
if [[ "${QUERY_ENVIRON}" != "" ]]; then
export DBUS_SESSION_BUS_ADDRESS="${QUERY_ENVIRON}"
fi
# Then we dispatch the command.
subcommand="$1"
if [[ -z "$subcommand" ]]; then
# No arguments given, print help.
sp-help
else
# Arguments given, check if it's a command.
if $(type sp-$subcommand > /dev/null 2> /dev/null); then
# It is. Run it.
shift
eval "sp-$subcommand $@"
else
# It's not. Try a search.
eval "sp-search $@"
fi
fi
@baudneo
Copy link

baudneo commented Jul 10, 2022

this is badass

@CallMeRush
Copy link

There are some issues with the search feature. I have updated it, and if anyone is interested in the changes I've published them here:
https://github.com/CallMeRush/sp

@Hashino
Copy link

Hashino commented Jul 22, 2022

i get the error:
Error org.freedesktop.DBus.Error.ServiceUnknown: The name org.mpris.MediaPlayer2.spotify was not provided by any .service files
using spotify on arch installed with yay
pacman -Qi spotify output:

Name            : spotify
Version         : 1:1.1.84.716-2
Description     : A proprietary music streaming service
Architecture    : x86_64
URL             : https://www.spotify.com
Licenses        : custom
Groups          : None
Provides        : None
Depends On      : alsa-lib>=1.0.14  gtk3  libxss  desktop-file-utils  openssl  nss
                  at-spi2-atk  libcurl-gnutls  libsm
Optional Deps   : ffmpeg4.4: Adds support for playback of local files
                  zenity: Adds support for importing local files
                  libnotify: Desktop notifications [installed]
Required By     : None
Optional For    : None
Conflicts With  : None
Replaces        : None
Installed Size  : 284.98 MiB
Packager        : Unknown Packager
Build Date      : Fri 22 Jul 2022 05:00:21 AM -03
Install Date    : Fri 22 Jul 2022 05:01:30 AM -03
Install Reason  : Explicitly installed
Install Script  : No
Validated By    : None

@bartosz-sporek
Copy link

i get the error: Error org.freedesktop.DBus.Error.ServiceUnknown: The name org.mpris.MediaPlayer2.spotify was not provided by any .service files using spotify on arch installed with yay pacman -Qi spotify output:

Name            : spotify
Version         : 1:1.1.84.716-2
Description     : A proprietary music streaming service
Architecture    : x86_64
URL             : https://www.spotify.com
Licenses        : custom
Groups          : None
Provides        : None
Depends On      : alsa-lib>=1.0.14  gtk3  libxss  desktop-file-utils  openssl  nss
                  at-spi2-atk  libcurl-gnutls  libsm
Optional Deps   : ffmpeg4.4: Adds support for playback of local files
                  zenity: Adds support for importing local files
                  libnotify: Desktop notifications [installed]
Required By     : None
Optional For    : None
Conflicts With  : None
Replaces        : None
Installed Size  : 284.98 MiB
Packager        : Unknown Packager
Build Date      : Fri 22 Jul 2022 05:00:21 AM -03
Install Date    : Fri 22 Jul 2022 05:01:30 AM -03
Install Reason  : Explicitly installed
Install Script  : No
Validated By    : None

Same here, any fix for this?

@olegrumiancev
Copy link

olegrumiancev commented Oct 19, 2022

There are some issues with the search feature. I have updated it, and if anyone is interested in the changes I've published them here: https://github.com/CallMeRush/sp

Hey there, your updated sp script works perfectly, thank you! I've added a pull request too, to add an option to look up playlists and start playing one if there's a match!

@CallMeRush
Copy link

Thanks for your contribution! It is now merged :)

@mohit-rathee
Copy link

mohit-rathee commented Jan 7, 2024

I was not able to search for music even after putting accurate id & secret. After some debugging I found the tokens sent in my requests are not equal to the tokens generated.
And here is how I fix the issue with the length of token:

Before:
241: ST2=${ST:15:86}}
After :
241: ST2=${ST:15:115}}

Actually the length of tokens are increased to 115. And that's the reason for invalid tokens.
Updated code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment