First prototype for better music playing skills

Hi, I made some progress with parsing song requests of the form: “play (album|track|song|) {album|track} by (artist|band|) {artist}”. Here is the crux of a function to do that (I’m at the stage of calling the Emby RESTful APIs):

def parse_phrase(phrase):
  album = "unknown-album"
  artist = "unknown-artist"
  found_by = "yes"                         # assume "by" is in the phrase
  intent_type = "unknown"                  # album, album-artist, artist, music, track, track-artist or unknown
  music = ""                               # used if not known to be "album", "track" or "artist"
  track = "unknown-track"                  # synonymous with "song"

  print("phrase = " + phrase)
  # remove leading "play" if found then split the request on the word "by"
  key = re.split("^play ", phrase)         # remove play if first word
  if len(key) == 2:                        # found first word "play"
    key = re.split(" by ", str(key[1]))
  else:
    key = re.split(" by ", phrase)
  if len(key) == 1:                        # did not find "by"
    music = str(key[0])
    if music == "any music" or music == "all music" or music == "my music" or music == "music":
      intent_type = "music"
    found_by = "no"
  elif len(key) == 2:                      # found one "by"
    music = str(key[0])
    artist = str(key[1])
  elif len(key) == 3:                      # found "by" twice - assume first one is in music
    music = str(key[0]) + " by " + str(key[1]) # paste the track or album back together
    artist = str(key[2])
  else:                                    # punt
    music = str(key[0])

  # look for (album|track|song|artist) in music
  key = re.split("^album ", music)         # remove "album" if first word
  if len(key) == 2:                        # found first word "album"
    album = str(key[1])                    # save album name
    if found_by == "yes":
      intent_type = "album-artist"
    else:
      intent_type = "album"
  else:                                     # leading "album" not found
    key = re.split("^track |^song ", music) # remove "track" or "song" if first word
    if len(key) == 2:                       # leading "track" or "song" found
      track = str(key[1])
      if found_by == "yes":
        intent_type = "track-artist"
      else:                                # assume track
        intent_type = "track"
    else:                                  # leading keyword not found
      key = re.split("^artist |^band ", music) # remove "artist" or "band" if first word
      if len(key) == 2:                    # leading "artist" or "band" found
        artist = str(key[1])
        if found_by == "yes":
          intent_type = "track-artist"
        else:
          intent_type = "artist"
      else:                                # no leading keywords found
        track = music
        if found_by == "yes":
          intent_type = "track-artist"
	...	  

Here are some tests - most of them work:

$ testparse.py
phrase = play album let it be by the beatles
==> call get_albums_by_artist(the beatles) then search for album 'let it be'
-------------------------
phrase = play down by the seaside by led zeppelin
==> call get_songs_by_artist(led zeppelin) and search for track 'down by the seaside'
-------------------------
phrase = play track down by the seaside
==> call get_songs_by_artist(the seaside) and search for track 'down'
-------------------------
phrase = play track let it be by artist the beatles
==> call get_songs_by_artist(artist the beatles) and search for track 'let it be'
-------------------------
phrase = play track let it be by band the beatles
==> call get_songs_by_artist(band the beatles) and search for track 'let it be'
-------------------------
phrase = play let it be by the beatles
==> call get_songs_by_artist(the beatles) and search for track 'let it be'
-------------------------
phrase = play let it be
==> call get_random_songs()
-------------------------
phrase = play song hey jude
==> call find_songs(hey jude)
-------------------------
phrase = play artist the beatles
==> call search_artist(the beatles) and play songs
-------------------------
phrase = play some music
==> call get_random_songs()
-------------------------

The test “play down by the seaside by led zeppelin” suceeds as it assumes the first “by” is in the track name. However, “play down by the seaside” fails as it assumes the track is “down” and the band is “by the seaside”. There could be code added to try a second call with just the entire track name.

I’m assuming this shouldn’t be moved into the common play skill, but rather, into the music playing skill,
emby.rickyphewitt, in my case. Is Ricky P Hewitt still involved with that skill?

Also, it’s hard coded to English. Supporting other languages should be doable, but would probably be tricky…

Anybody have any suggestions?

Thanks.

-Mike Mac

Mike, i adress the question here cause it does not remotely belong in the other thread. Artist,band,… are simply *.voc files (Adapt intent). en-us: artist.voc with the entry artist, de-de: artist.voc with the entry interpret (use .one_of() in the constructor)

You check then

if "artist" in message.data

by that you will know if the item in question is an artist in every language supported. Or simply make one intent each item type.

When it comes to common_play it’s a bit fiddly. I do it like that

translations = {IntentType.ARTIST: self.translate_list("artist"),
                IntentType.ALBUM: self.translate_list("album"),
                IntentType.PLAYLIST: self.translate_list("playlist"),
                IntentType.SONG: self.translate_list("songs")}

intent = IntentType.from_common_phrase(phrase, translations)

IntenType is enum.Enum. Data for translate_list sits in …/dialog/… with .list extension and you will get a list of that entity

@SGee thanks for the quick reply. I’ll continue getting the code working in English, then this post will be most helpful towards internationalizing it.

-Mike Mac