TLDR: This article demonstrates how to use AppleScript to control Spotify and display song information in a tmux status bar.
Tmux is a terminal multiplexer: it enables several terminals to be created, accessed, and controlled from a single screen. Tmux may be detached from a screen and continue running in the background, then later reattached.
source: https://github.com/tmux/tmux
AppleScript is a scripting language created by Apple Inc. that facilitates automated control over scriptable Mac applications. The term "AppleScript" may refer to the language itself, to an individual script written in the language, or, informally, to the macOS Open Scripting Architecture that underlies the language.[4][5]
INTRO
I have been using Tmux in my workflow with the https://github.com/gpakosz/.tmux configuration. I've done further research into how to personalize it. Unfortunately, most plugins are written in a shell script, which is understandable as shells like bash are available (or built-in) in most distros or OSs. I do want to learn how to write shell scripts, not just for Tmux, but as well as the universality of it.
NOTE: AppleScripts will only work on a macOS ecosystem.
Let's create a script that's human-readable using AppleScript to get a feel for the language and then expand on it by utilizing a shell script afterwards.
IMPLEMENTATION: SETUP
The purpose of the following script is to explore the AppleScript language and how to utilize it to interact with the Spotify app. Additionally, we aim to display the current song information (track and artist name), as well as the player's state (song playing or paused), and whether Spotify has been launched and displays all this data in a tmux status bar.
As for most script files, the heading should be the environment that the file should be run in:
#!/usr/bin/env osascript
First, we must ascertain whether Spotify is running; if it isn't, display that Spotify is currently inactive. Take note of the language's readability and the keywords employed.
tell application "Spotify" if it is running then # else "♫ 💤" end if end
Now, we want to obtain and store the current track and artist's name and the player state (playing or paused) in variables, specifically, track_name, artist_name, and player_state, respectively. We could also retrieve other information, such as the album_name,
tell application "Spotify" if it is running then + set track_name to name of current track + set artist_name to artist of current track + set player_state to player state as string else "♫ 💤" end if end
We now want to display the info we gathered from above. We can use an if/else statement to branch between what we display based on the player state.
tell application "Spotify" if it is running then set track_name to name of current track set artist_name to artist of current track set player_state to player state as string + if player_state is equal "playing" + "♫ ⏵ " & track_name & " + " & artist_name + else + "♫ ⏸ " & track_name & " + " & artist_name + end if else "♫ 💤" end if end
And that's it. However, we can slightly refactor the code and introduce functions. Finally, let's include the maintainer's information (optional, but helpful for directing complaints when the script doesn't work as intended) and a description of the script's function for users.
#!/usr/bin/env osascript + -- maintainer: cdrani + -- Returns Spotify current player state and song info + on getPlayerState(player_state) + if player_state is "playing" then return "♫ ⏵ " + if player_state is "paused" then return "♫ ⏸ " + end getPlayerState + on printSongInfo(player_state, track_name, artist_name) + getPlayerState(player_state) & track_name & " - " & + artist_name + end printSongInfo tell application "Spotify" + if it is not running then + return "♫ 💤" + end set track_name to name of current track set artist_name to artist of current track set player_state to player state as string + my printSongInfo(player_state, track_name, artist_name) end tell
Now, we want to make the script executable. I saved mine as "spotify.scpt" (
scpt
is the filename for AppleScript) in a scripts directory (which I am currently inside):chmod +x ~/scripts/spotify.scpt
The final step is to integrate it into our Tmux status line. I opted for the right-hand side. Inside a Tmux session, enter your command prompt using
Prefix + :
and type the following based on the path the script fileset -g status-right '#(~/scripts/spotify.scpt)'
Great! Implementing this idea is quite simple, from conception to full realization, except for the need to close each 'tell' and 'if' statement. The song information is displayed in the top-right corner and updates upon song change, although there is a slight ~2-second delay.
AppleScript Editor
To begin, it's a good idea to use the AppleScript Editor, as it offers language features such as syntax highlighting, error compilation, quick access to language documentation, and more. Below is the aforementioned script within the editor. In the editor, we build the script to verify that there are no issues, and then we can run it, with errors and results displayed in the bottom pane.
JavaScript Editor
AppleScript also supports JavaScript. Our script can be rewritten in JavaScript:
// maintainer: cdrani
// Returns the current player state and song info for Spotify
function getPlayerState(playerState) {
if (playerState === "playing") return "♫ ⏵ ";
if (playerState === "paused") return "♫ ⏸ ";
}
function printSongInfo(playerState, trackName, artistName) {
return getPlayerState(playerState) + trackName + " - " + artistName;
}
const spotify = Application("Spotify");
if (!spotify.running()) {
"♫ 💤";
} else {
const trackName = spotify.currentTrack.name();
const artistName = spotify.currentTrack.artist();
const playerState = spotify.playerState();
printSongInfo(playerState, trackName, artistName);
}
Similar to the first script, save this one as ~/scripts/spotify.js. You can now run it in your Tmux command prompt. Since this is a JS file, you need to inform AppleScript about it:
-l language | Override the language for any plain text files. Normally, plain text files are compiled as AppleScript.
set -g status-right '#(osascript -l JavaScript ~/scripts/spotify.js)'
The integration of our scripts into Tmux via the command prompt is temporary and gets cleared when the session or server is terminated. To make it permanent, we need to transfer it to our configuration file, typically saved as ~/.tmux.conf, and then source the config file to apply the changes.
echo "set -g status-right '#(~/scripts/spotify.scpt)'" >> .tmux.conf
tmux source-file ~/.tmux.conf
Bonus Feature
Announcement Feature
The final feature we will incorporate enables the announcement of the current song, as well as its play/pause status, whenever the song changes.
Let's first introduce a new
say
, which will speak out loud our songInfo.+ on sayPlayerState(player_state, track_name, artist_name) + say player_state & " " & track_name & " by " & artist_name + end sayPlayerState tell application "Spotify" -- truncated + sayPlayerState(player_state, track_name, artist_name) my printSongInfo(player_state, track_name, artist_name) end tell
An issue with the above text is that the song information is repeated multiple times per song. This is likely because new data is being fed into our script every few seconds, based on the current song's state - its name, artist, whether it's playing or paused, etc. We want to limit this repetition to only occur when the song changes or the song's play/pause state changes. Let's track the song and state by defining their properties set to "missing value". jj
-- properties to track song and state; set to non value + property previous_song : missing value + property previous_state : missing value --- + set stateExistsAndIsDifferent to previous_state is equal to missing value or previous_state is not equal to player_state + set isPlayingAndSongChanged to previous_state is "playing" and previous_song is not equal to track_name + -- update previous_state and previous_song with new states + if (stateExistsAndIsDifferent or isPlayingAndSongChanged) + set previous_state to player_state + set previous_song to track_name + sayPlayerState(player_state, track_name, artist_name) + + -- send command to update tmux status + do shell script "tmux set-option -g status-right '" & my printSongInfo(player_state, track_name, artist_name) & "'" + end if
Finally, continuously refresh the data by running the script in a loop with a 1-second delay.
#!/usr/bin/env osascript # maintainer: cdrani # Returns the current player state and song info for Spotify property previous_song : missing value property previous_state : missing value on getPlayerState(player_state) if player_state is "playing" then return "♫ ⏵ " if player_state is "paused" then return "♫ ⏸ " end getPlayerState on printSongInfo(player_state, track_name, artist_name) getPlayerState(player_state) & track_name & " - " & artist_name end printSongInfo on sayPlayerState(player_state, track_name, artist_name) say player_state & " " & track_name & " by " & artist_name end sayPlayerState + repeat tell application "Spotify" if it is not running then return "♫ 💤" end if set track_name to name of current track set artist_name to artist of current track set player_state to player state as string end tell set stateExistsAndIsDifferent to previous_state is equal to missing value or previous_state is not equal to player_state set isPlayingAndSongChanged to previous_state is "playing" and previous_song is not equal to track_name if (stateExistsAndIsDifferent or isPlayingAndSongChanged) set previous_state to player_state set previous_song to track_name sayPlayerState(player_state, track_name, artist_name) do shell script "tmux set-option -g status-right '" & my printSongInfo(player_state, track_name, artist_name) & "'" end if + delay 1 + end repeat
Conclusion
This was a simple script to showcase AppleScript and to show how it can integrate with the Apple ecosystem, here focusing on extracting Spotify info to display in a Tmux status bar. In the next article, we will expand on it to add additional controls such as next, previous, pause/play, and repeat.