Apple Music CLI with ChatGPT

It’s always useful to be able to manage things from the terminal, for some of us it’s even a thing about comfort. I used ChatGPT to generate a simple set of alias functions to work as CLI for Apple Music.

The first thing to do would be to define the list of functionalities we are going to be asking ChatGPT to perform for us, these functionalities should be enough to properly handle the basic usage scenarios however providing a proper and extensible format so we can add more complicated tasks over time.

Common functionalities

The basic functionalities I would expect from a sensible music player are:

Mapping of functionalities

The proposal would be for ChatGPT to generate the following mapping for the functions described into aliases:

Functionality Alias Usage
Play music-play Plays the current track
Pause music-pause Pauses the current track
Skip to next track music-next Skips to the next track in queue
Go back to previous track music-prev Goes back to the previous track in the queue
Search and play track music-play-track <track name> Searches for <track name> in Apple Music and, if found, plays it
Display current track information music-now-playing Shows information about the currently-playing track in Apple Music

Asking ChatGPT for scripting superpowers!

The prompt I used for generating the tools:

Me: Hey! Can you write me a set of shell aliases for performing normal tasks from the terminal like a CLI for Apple Music?

I had to go back-and-forth for a while with our Chat friend as there were some issues (explained after the script) regarding the music-now-playing function which wasn’t working at the first try. I would have been really surprised if it generates a perfect working example on the first attempt, honestly.

The resulting script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#!/bin/bash
# Apple Music Aliases- @humbertowoody
# Creates aliases for Apple Music commands.

# Function for playing music
function music-play()
{
    osascript -e 'tell application "Music" to play';
}

# Function for pausing music
function music-pause()
{
    osascript -e 'tell application "Music" to pause';
}

# Function for skipping to the next track
function music-next()
{
    osascript -e 'tell application "Music" to next track';
}

# Function for skipping to the previous track
function music-prev()
{
    osascript -e 'tell application "Music" to previous track';
}

# Function for searching and playing a music-play-track
function music-play-track()
{
  query="$1"
  osascript -e "tell application \"Music\"
    set search_results to search playlist \"Library\" for \"$query\"
    if search_results is not {} then
      set best_result to item 1 of search_results
      play best_result
    end if
  end tell"
}

# Function for getting information about the currently playing track
function music-now-playing() {
    # Get the current track properties
    track_name=$(osascript -e 'tell application "Music" to get name of current track as string' 2>/dev/null || echo "(not present)");
    artist_name=$(osascript -e 'tell application "Music" to get artist of current track as string' 2>/dev/null || echo "(not present)");
    album_name=$(osascript -e 'tell application "Music" to get album of current track as string' 2>/dev/null || echo "(not present)");
    track_number=$(osascript -e 'tell application "Music" to get track number of current track as integer' 2>/dev/null || echo "(not present)");
    disc_number=$(osascript -e 'tell application "Music" to get disc number of current track as integer' 2>/dev/null || echo "(not present)");
    duration_seconds=$(osascript -e 'tell application "Music" to get duration of current track as integer' 2>/dev/null || echo "(not present)");
    play_count=$(osascript -e 'tell application "Music" to get played count of current track as integer' 2>/dev/null || echo "(not present)");
    rating=$(osascript -e 'tell application "Music" to get rating of current track as integer' 2>/dev/null || echo "(not present)");
    loved=$(osascript -e 'tell application "Music" to get loved of current track as boolean' 2>/dev/null || echo "(not present)");
    genre=$(osascript -e 'tell application "Music" to get genre of current track as string' 2>/dev/null || echo "(not present)");
    year=$(osascript -e 'tell application "Music" to get year of current track as integer' 2>/dev/null || echo "(not present)");
    art_file=$(osascript -e 'tell application "Music" to get location of artwork 1 of current track as string' 2>/dev/null || echo "(not present)");
    track_quality=$(osascript -e 'tell application "Music" to get bit rate of current track as integer' 2>/dev/null || echo "(not present)");
    sampling_rate=$(osascript -e 'tell application "Music" to get sample rate of current track as integer' 2>/dev/null || echo "(not present)");

    # Format the duration as minutes:seconds
    if [[ -n "$duration_seconds" ]]; then
        duration_min=$(( duration_seconds / 60 ))
        duration_sec=$(( duration_seconds % 60 ))
        duration=$(printf "%d:%02d" $duration_min $duration_sec)
    else
        duration="(not present)"
    fi

    # Print the current track information with decorations
    printf "\n%s\n" "--------------------------------------"
    printf "%s\n" "            Now playing"
    printf "%s\n" "--------------------------------------"
    printf "  %-15s %s\n" "Name:" "$track_name"
    printf "  %-15s %s\n" "Artist:" "$artist_name"
    printf "  %-15s %s\n" "Album:" "$album_name"
    printf "  %-15s %s\n" "Track number:" "$track_number"
    printf "  %-15s %s\n" "Disc number:" "$disc_number"
    printf "  %-15s %s\n" "Duration:" "$duration"
    printf "  %-15s %s\n" "Play count:" "$play_count"
    printf "  %-15s %s\n" "Rating:" "$rating"
    printf "  %-15s %s\n" "Loved:" "$( [[ $loved == true ]] && echo "yes" || echo "no" )"
    printf "  %-15s %s\n" "Genre:" "$genre"
    printf "  %-15s %s\n" "Year:" "$year"
    printf "  %-15s %s\n" "Artwork file:" "$art_file"
    printf "  %-15s %s kbps\n" "Track quality:" "$track_quality"
    printf "  %-15s %s Hz\n" "Sampling rate:" "$sampling_rate"
    printf "%s\n" "--------------------------------------"
}


# Helper function to test which track properties are available
# This is useful for debugging
function music-now-playing-test() {
    # Define the track properties to test
    properties=(
        "name"
        "artist"
        "album"
        "track number"
        "disc number"
        "duration"
        "play count"
        "rating"
        "loved"
        "genre"
        "year"
        "artwork location"
    )

    # Test each track property and output the results
    printf "Music track properties:\n\n"
    for prop in "${properties[@]}"; do
        # Get the value of the property
        prop_value=$(osascript -e "tell application \"Music\" to get ${prop} of current track" 2>/dev/null)

        # Print the property name and whether it was available
        if [[ -n "$prop_value" ]]; then
            printf "%-16s%s\n" "${prop}:" "available"
        else
            printf "%-16s%s\n" "${prop}:" "not available"
        fi
    done

    printf "\n"
}

# Function that prints all aliases with examples
function music-help()
{
    echo "music-play: Play music"
    echo "music-pause: Pause music"
    echo "music-next: Skip to the next track"
    echo "music-prev: Skip to the previous track"
    echo "music-play-track: Search and play a track"
    echo "music-now-playing: Get information about the currently playing track"
}

The solution involves two things:

  1. functions to map the commands themselves, that way we cannot get confused with the quote/double-quote dilemma.
  2. osascript, an Apple Script execution engine that, as you can see, receive commands in a really abstract manner, almost plain language1.

The music-now-playing issues

As you may have observed, the provided script includes a function called music-now-playing which pretty-prints in the terminal information from the currently-playing track.

There’s also a function called music-now-playing-test, this is because, depending on Apple Music’s version, there’s no guarantee on the information available to the scripting engine from Apple.

The idea is for you to use the music-now-playing-test alias to debug which features are available in order for you to modify the music-now-playing function accordingly.

The final usage

These aliases and functions live in my dotfiles repository in GitHub in which I keep my entire configuration. From there you can infer how I’m using them, but basically I load them during ZSH initialization from .zshrc and source through each tools/*.sh file (such as this one: apple-music-aliases.sh) and voilá!

This is just a fun perk to have available on the command line. Here you can see the output of the music-now-playing alias function:

❯ music-now-playing
Now playing:
  Name:           Symphony No. 2 in D Major, Op. 36: III. Scherzo. Allegro
  Artist:         London Symphony Orchestra & Josef Krips
  Album:          Beethoven: The Complete Symphony Collection
  Track number:   7
  Disc number:    1
  Duration:       3:32
  Play count:     2
  Rating:         0
  Loved:          no
  Genre:          Classical
  Year:           1960
  Artwork file:   (not present)
  1. AppleScript is a scripting language created by Apple Inc. that facilitates automated control over scriptable Mac applications. First introduced in System 7, it is currently included in all versions of macOS as part of a package of system automation tools. 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. – https://en.wikipedia.org/wiki/AppleScript