Show cmus and ncspot status in Sway

last updated 2023-01-15 14:57:45 by Simon Vandevelde

To play music on my pc, I use two different applications:

  • cmus, for my local music
  • ncspot, to connect to Spotify
Both are minimalistic applications that work right out of your terminal. It would be cool, I thought, to show their status on my Sway status bar. So, I spent some time figuring out how to best do so. If you read this post, you can do so too!

It's actually fairly simple: I have a status.sh script which is called by swaybar to update the text every second (see Pomodoro timer in Sway). You can do this by adding the following line to the bar section of your sway config:

status_command while ~/.config/sway/status.sh; do sleep 1; done
All we need to do is add some code to this script to also display cmus and ncspot. If you don't want to read, you can also view the script directly.

cmus

Collecting the current information from cmus is luckily very easy, thanks to cmus's built-in controller.

$ cmus-remote -C  # Returns 1 if cmus is running
$ cmus-remote -Q
status playing
file /home/saltfactory/Music/Mort Garson/Music from Patch Cord Productions/10 Music For Advertising #4.mp3
duration 66
position 4
tag artist Mort Garson
tag album Music From Patch Cord Productions
tag title Music For Advertising #4
tag date 2020
tag originaldate 2020
tag genre Electronic
tag discnumber 1
tag tracknumber 10
tag albumartist Mort Garson
tag artistsort Garson, Mort
tag albumartistsort Garson, Mort
tag albumsort Music from Patch Cord Productions
tag label Sacred Bones Records
tag publisher Sacred Bones Records
tag musicbrainz_trackid 18e0097a-ca9f-4128-b205-ae6d7889a328
tag media CD
set aaa_mode all
set continue true
set play_library true
set play_sorted true
set replaygain disabled
set replaygain_limit true
set replaygain_preamp 0.000000
set repeat false
set repeat_current false
set shuffle tracks
set softvol false
set vol_left 100
set vol_right 100
As you can see, this command allows us to see all revelant information (and more!).

All we then need to do is collect the information we want to display whenever cmus is running. In my case, I want to know the artist and title of the current number, as well as if cmus is playing or paused. In our status.sh script, this is done as follows.

if cmus-remote -C ;
then
    # If cmus is active, get the information and display it.
    # cmus information
    current_artist=$(cmus-remote -Q | grep "\sartist\s" | cut -d " " -f 3-)
    current_title=$(cmus-remote -Q | grep "\stitle\s" | cut -d " " -f 3-)
    cmus_playing=$(cmus-remote -Q | grep "status\s" | cut -d " " -f 2-)
fi

This makes it very easy to add this information to our status output, but only whenever cmus is actually open. For example:

if [ $ cmus_playing = 'playing' ];
then
    # Show a play or pause symbol.
    playing='▶';
else
    playing='⏸';
fi

if [ "$ current_artist" = "" ];
then
    # If cmus isn't open, only show date.
    echo $ date_formatted;
else
    echo $ playing $ current_artist - $ current_title ♫ '|' $ date_formatted;
fi
(take care to remove the whitespace after each dollar!)

ncspot

ncspot is a bit more tricky to set up, as it does not have a cmus-remote-equivalent. It does, however, have a UNIX socket which you can connect to with, e.g., netcat. The downside of this approach is that the connection through netcat stays open -- it keeps listening for changes, and outputs them to the stdout. This is annoying, as we are only interested in the currently playing information, and thus want to close the connection right after we have received our first bit of data.

As it turns out, there are multiple netcat implementations, each with their own features. Luckily for use, the OpenBSD variant of netcat supports a -W flag, which closes the connection after x amount of bytes have been transfered. We can use this as follows:

$ nc -W 1 -U ~/.cache/ncspot/ncspot.sock
{"mode":{"Playing":{"secs_since_epoch":1673790531,"nanos_since_epoch":558149619}},"playable":{"type":"Track","id":"2fqnivbjstZvlCcXjpu3Tw","uri":"spotify:track:2fqnivbjstZvlCcXjpu3Tw","title":"Tu pu du cu - Bonus Track","track_number":16,"disc_number":1,"duration":172872,"artists":["Odezenne"],"artist_ids":["1FiWBzw15KbR9amOb1Xnxp"],"album":"OVNI (Orchestre virtuose national incompétent) [Edition bonus Louis XIV]","album_id":"5Hl4kW2racfsaeBKtod3pz","album_artists":["Odezenne"],"cover_url":"https://i.scdn.co/image/ab67616d0000b2733a0b4a45a983e0166b998bcc","url":"https://open.spotify.com/track/2fqnivbjstZvlCcXjpu3Tw","added_at":"2021-12-07T19:03:50Z","list_index":0}}
This outputs all known information on the currently playing track in a json format. Using the json parser jq, we can structure this a bit more neatly.
$ nc -W 1 -U ~/.cache/ncspot/ncspot.sock | jq
{
  "mode": {
    "Playing": {
      "secs_since_epoch": 1673790531,
      "nanos_since_epoch": 558149619
    }
  },
  "playable": {
    "type": "Track",
    "id": "2fqnivbjstZvlCcXjpu3Tw",
    "uri": "spotify:track:2fqnivbjstZvlCcXjpu3Tw",
    "title": "Tu pu du cu - Bonus Track",
    "track_number": 16,
    "disc_number": 1,
    "duration": 172872,
    "artists": [
      "Odezenne"
    ],
    "artist_ids": [
      "1FiWBzw15KbR9amOb1Xnxp"
    ],
    "album": "OVNI (Orchestre virtuose national incompétent) [Edition bonus Louis XIV]",
    "album_id": "5Hl4kW2racfsaeBKtod3pz",
    "album_artists": [
      "Odezenne"
    ],
    "cover_url": "https://i.scdn.co/image/ab67616d0000b2733a0b4a45a983e0166b998bcc",
    "url": "https://open.spotify.com/track/2fqnivbjstZvlCcXjpu3Tw",
    "added_at": "2021-12-07T19:03:50Z",
    "list_index": 0
  }
}

Alright, so just like with cmus, it's now pretty easy to add this to our status.sh.

if nc -W 1 -U ~/.cache/ncspot/ncspot.sock > /dev/null;
then
    # ncspot outputs json through the socket -- use jq to grab the first
    # artist and the title of the current song.
    # We use `eval` to strip the quotation marks around the output.
    ncspot_info=$(nc -W 1 -U ~/.cache/ncspot/ncspot.sock);
    eval current_artist=$(echo  | jq '.playable.artists[0]')
    eval current_title=$(echo  | jq '.playable.title')
    eval ncspot_playing=$(echo  | jq '.mode.Playing.secs_since_epoch');
fi
We can combine this together with the previous cmus code as follows:
if [  != null ] || [  = 'playing' ];
then
    playing='▶';
else
    playing='⏸';
fi
And that's it! Now everything should work.

You can view the full status.sh code here.