declension / squeeze-alexa

Squeezebox integration for Amazon Alexa
GNU General Public License v3.0
59 stars 20 forks source link

Support non-native (e.g. iTunes) LMS playlists #111

Open htcheng opened 5 years ago

htcheng commented 5 years ago

Not sure if anyone else is having this issue, but I have LMS running on my Synology NAS and it imports all of the music and playlists from an iTunes directory which I have sync'd via Syncthing with an iMac that my kids use to load/manage all of their music via iTunes. Allows them to use iTunes as a UI and master library and have the NAS keep a live copy (we also back up the iMac using Time Machine just in case).

LMS automatically updates when changes are detected in the directory and both iTunes and LMS have the same music and playlists. Playlists created on the iMac in iTunes are automatically imported into LMS and can then be used across all of the Squeezebox Booms, Touches, Radios, piCorePlayers, etc. in the house. Everything works perfectly and everyone is happy...

I've gotten Squeeze-Alexa working as well and we can control LMS via Alexa perfectly EXCEPT for some weird reason, when we request to Alex to play any of the playlists that are imported from iTunes (and work pefectly when selected in the LMS web UI), Alexa understands the command and performs the command on LMS, but the resulting playlist is empty every time no matter how large or small the playlist is. I still need to test whether playlists that are created within LMS work or not, but has anyone else come across this issue?

htcheng commented 5 years ago

Ok, update here. Playlists I created in LMS also don't work, so I think the playlist functionality is broken in squeeze-alexa.

My python is pretty basic, but I think I've traced this problem to the way that Squeeze-Alexa is implementing the LMS CLI command to play playlists:

def playlist_play(self, path, player_id=None):
        """Play song / playlist immediately"""
        self.player_request("playlist play %s" % (urllib.quote(path)),
                            player_id=player_id)

In the past when I've been sending LMS CLI commands relating to playlists, I noticed that playlist play fails to work in many cases, resulting in empty playlists and playlists not playing which is the behavior that I'm seeing with squeeze-alexa.

What I've ended up using is the playlistcontrol cmd:load playlist_name:[playlistname] command and this successfully loads large playlists.

I'd make this change myself, but it appears that squeeze-alexa uses the same command to play songs as playlists and I don't really know python well enough to make the changes without messing things up at this point. I might have to invest in learning python to debug this, but help to separate out the commands to execute playing songs vs playlists would be very helpful!

declension commented 5 years ago

@htcheng thanks for investigating. Nice setup too!

I can't replicate the problem, playing playlists works fine here. Are they particularly large (though I don't see why this would make any difference)? What path are your LMS audio files kept at?

Can you post some logs from Cloudwatch, taking out anything sensitive e.g. passwords or IPs?

htcheng commented 5 years ago

@declension

Thanks for the quick response!

I tested again with an LMS created playlist vs iTunes imported playlist and you're right, it does work. I've attached the Cloudwatch logs below - but that's not the source of the error. I've found the error is related to how LMS stores iTunes imported playlists in the database not as a playlist file like other LMS playlists.

See the CLI console query below - there is no URL associated with iTunes playlists (just "itunesplaylist:[name of playlist]", so that's why they are empty! I noticed that the LMS UI interface does play the playlists and the UI uses the "playlistcontrol cmd:load" command to load these playlists... so this explains the behavior. The LMS created playlist I created is named "Test" and this playlist has a URL associated with it that points to a local .m3u file -- this works with your existing code.

The solution is to separate out the handling of playlists and use the 'playlistcontrol' CLI command to load playlists rather than 'playlist play' - this command works with both LMS playlists and iTunes imported playlists. Why Logitech implemented the commands differently makes no sense...

This doesn't look like that difficult of a change -- if only I knew python! I think it's time to start learning python, but I'm a little short on time right now, so help is appreciated here to fix this.


playlists 0 255 tags:u playlists 0 255 tags:u id:240194 playlist:90’s Music url:itunesplaylist:90's Music id:240174 playlist:All (Charles) url:itunesplaylist:All (Charles) id:240190 playlist:Alternative url:itunesplaylist:Alternative id:240172 playlist:Audiobooks url:itunesplaylist:Audiobooks id:240175 playlist:Classical url:itunesplaylist:Classical id:240170 playlist:Downloaded url:itunesplaylist:Downloaded id:240176 playlist:Easy url:itunesplaylist:Easy id:240182 playlist:Everyday url:itunesplaylist:Everyday id:240177 playlist:Favorites url:itunesplaylist:Favorites id:240191 playlist:Folk url:itunesplaylist:Folk id:240183 playlist:Hegedu/Klasszikus url:itunesplaylist:Hegedu/Klasszikus id:240186 playlist:Instrumental url:itunesplaylist:Instrumental id:240192 playlist:Jazz url:itunesplaylist:Jazz id:240173 playlist:Charles url:itunesplaylist:Charles id:240178 playlist:Charles Classical Short url:itunesplaylist:Charles Classical Short id:240184 playlist:Mindig Mindehol url:itunesplaylist:Mindig Mindehol id:240181 playlist:Sabrina url:itunesplaylist:Sabrina id:240195 playlist:My Top Rated url:itunesplaylist:My Top Rated id:240187 playlist:Kent url:itunesplaylist:Kent id:240188 playlist:Kent 1 url:itunesplaylist:Kent 1 id:240189 playlist:Playlists for Squeezebox url:itunesplaylist:Playlists for Squeezebox id:240171 playlist:Podcasts url:itunesplaylist:Podcasts id:240179 playlist:Popper Etudes url:itunesplaylist:Popper Etudes id:240185 playlist:Relax url:itunesplaylist:Relax id:240193 playlist:Rock url:itunesplaylist:Rock id:240180 playlist:Sleep url:itunesplaylist:Sleep id:225220 playlist:SyncOptions-Dining Room-00 04 20 22 cc a8 url:file:///volume2/music/LMS Playlists/SyncOptions-Dining Room-00_04_20_22_cc_a8.m3u id:225236 playlist:SyncOptions-Kitchen-00 04 20 1e ec 9f url:file:///volume2/music/LMS Playlists/SyncOptions-Kitchen-00_04_20_1e_ec_9f.m3u id:225237 playlist:SyncOptions-Kents Radio-00 04 20 28 2c 62 url:file:///volume2/music/LMS Playlists/SyncOptions-Kents Radio-00_04_20_28_2c_62.m3u id:225238 playlist:SyncOptions-Kents Stereo-80 1f 02 da 61 cb url:file:///volume2/music/LMS Playlists/SyncOptions-Kents Stereo-80_1f_02_da_61_cb.m3u id:225239 playlist:SyncOptions-Kents Stereo-b8 27 eb fc e6 d2 url:file:///volume2/music/LMS Playlists/SyncOptions-Kents Stereo-b8_27_eb_fc_e6_d2.m3u id:225240 playlist:SyncOptions-Upstairs MBR-Bath-02 00 12 19 57 04 url:file:///volume2/music/LMS Playlists/SyncOptions-Upstairs MBR-Bath-02_00_12_19_57_04.m3u id:240197 playlist:Szerelmes körtefa url:itunesplaylist:Szerelmes k%C3%B6rtefa id:240199 playlist:Test url:file:///volume2/music/LMS Playlists/Test.m3u id:240196 playlist:Top 25 Most Played url:itunesplaylist:Top 25 Most Played count:35 playlist load Test 00:04:20:26:2a:19 playlist load/volume2/music/LMS Playlists/Test.m3u # WORKS! playlist clear 00:04:20:26:2a:19 playlist clear playlistcontrol cmd:load playlist_name:Test 00:04:20:26:2a:19 playlistcontrol cmd:load playlist_name:Test count:4 # WORKS! playlist load Charles 00:04:20:26:2a:19 playlist load Charles # DOES NOT WORK! NOTICE LACK OF PATH/URL ON RESPONSE OR COUNT playlistcontrol cmd:load playlist_name:Charles 00:04:20:26:2a:19 playlistcontrol cmd:load playlist_name:Charles count:573 # WORKS! NOTICE # TRACKS IN RESPONSE

Cloudwatch log

htcheng commented 5 years ago

@declension - so I took a more careful look at the code and with my limited python skills, I think I've figured out a solution. I saw that instead of using the playlist_play function, you actually were calling playlist_resume and feeding in the name of the playlist.

So I added a function called playlistcontrol_load in squeezebox/server.py which utilized the other method of loading playlists in the CLI:

def playlistcontrol_load(self, name, player_id=None):
    cmd = ("playlistcontrol cmd:load playlist_name:%s"
           % (urllib.quote(name)))
    self.player_request(cmd, player_id=player_id)

and modified line 303 in main.py to call playlistcontrol_load instead of playlist_resume:

            server.playlistcontrol_load(pl, player_id=pid)

and Voila! Alexa now plays both LMS created playlists and iTunes imported playlists using squeeze-alexa...

I'd submit a pull request if I knew how, but given that I only changed a very few lines of code - this should be easy to test and incorporate. Seems to solve my problem without breaking anything...

Hope this helps.

declension commented 5 years ago

@htcheng thanks for that.

I'm still not sure why playlistcontrol cmd:load should work rather than playlist resume. Though also I can't recall now why I moved away from using the more obvious playlist play. Can you check if that works for you? I'd rather use the more standard commands than ones designed for ID-based operations (see the docs)

Furthermore from your LMS logs above I see, assuming they're unprocessed, that the URLs being passed around are not properly URL encoded: e.g. 90’s Music not 90%E2%80%99s%20Music and various others with spaces in the names. This isn't the first problem with iTunes multi-word playlists

I've borrowed those playlist names for a test (let me know if that's a problem!) and can confirm squeeze-alexa only parses the bit before the space (as designed). This would explain why those playlists are never playing (your recent tests above are all single-word playlists, so would not be affected by this).

htcheng commented 5 years ago

@declension

My apologies, in the LMS logs above I removed some of the URL encoding to make it more human-readable, but rest-assured the names are URL encoded when they come back from the LMS server and do work with squeeze-alexa - except some of the longer compound ones like "Charles Classical Short" playlist get interpreted as "Classical" playlist, so I will have to change some of the names of the playlists to ones that are clearer. "90's Music" playlist does get interpreted correctly though Alexa calls it "90 (ess) Music" playlist

As for playlist play that doesn't work with iTunes playlists either since it doesn't have a URL to play. The playlist function in general expects LMS playlists or .m3u playlist files. What I think happened is that the iTunes import functionality was added later and instead of importing iTunes playlists as normal LMS or .m3u files they put it in the database. Not sure why as that seems harder, but that appears the path they took.

BTW, the playlistcontrol cmd:load playlist_name: command is the one that the LMS UI uses itself to play playlists.

declension commented 5 years ago

Ah right. Though itunesplaylist:Everyday is a URL technically (custom scheme, a bit like news).

I guess as you say the problem is that those original commands expect a native playlist / playlist file on disk as they predate LMS plugins providing their own schemes for playable URLs / playlists.

I'll look into moving to the playlistcontrol especially if the UI now does this too. Thanks again for your help getting to the bottom of it.