prep for playlist editing
authormarcelveldt <marcelvanderveldt@MacBook-Silvia.local>
Fri, 25 Oct 2019 21:40:23 +0000 (23:40 +0200)
committermarcelveldt <marcelvanderveldt@MacBook-Silvia.local>
Fri, 25 Oct 2019 21:40:23 +0000 (23:40 +0200)
music_assistant/models/musicprovider.py
music_assistant/models/player.py
music_assistant/music_manager.py
music_assistant/musicproviders/file.py
music_assistant/musicproviders/spotify.py
music_assistant/playerproviders/squeezebox.py
music_assistant/web.py

index f40855c75a3270002a41baeb936058044a5a3ffa..2cc4c9a2a1f30010eefc00466d739135f741f22e 100755 (executable)
@@ -245,7 +245,7 @@ class MusicProvider():
             searchstr = "%s - %s" %(searchartist.name, searchtrack.name)
             search_results = await self.search(searchstr, [MediaType.Track], limit=5)
             for item in search_results["tracks"]:
-                if item.name == searchtrack.name and item.version == searchtrack.version and item.album.name == searchtrack.album.name:
+                if item and item.name == searchtrack.name and item.version == searchtrack.version and item.album.name == searchtrack.album.name:
                     # double safety check - artist must match exactly !
                     for artist in item.artists:
                         if artist.name == searchartist.name:
index be284b976e3346db2589ff6250306a65d1d5cba1..ffe81558759c71d02dc8900d09cd936d20818f90 100755 (executable)
@@ -500,6 +500,11 @@ class Player():
 
     async def volume_set(self, volume_level):
         ''' [PROTECTED] send new volume level command to player '''
+        if isinstance(volume_level, str):
+            if '+' in volume_level or 'up' in volume_level.lower():
+                return await self.volume_up()
+            elif '-' in volume_level or 'down' in volume_level.lower():
+                return await self.volume_down()
         volume_level = try_parse_int(volume_level)
         # handle group volume
         if self.is_group:
index 632ae3f46dce78d9bd9048b817175af0dd0c00ac..e1a9d42f63ea86303bfb20cde4047fc68d799e2a 100755 (executable)
@@ -200,18 +200,20 @@ class MusicManager():
     async def item_action(self, item_id, media_type, provider, action, action_details=None):
         ''' perform action on item (such as library add/remove) '''
         result = None
-        item = await self.item(item_id, media_type, provider)
+        item = await self.item(item_id, media_type, provider, lazy=False)
         if item and action in ['library_add', 'library_remove']:
             # remove or add item to the library
-            for prov_mapping in result.provider_ids:
+            for prov_mapping in item.provider_ids:
                 prov_id = prov_mapping['provider']
                 prov_item_id = prov_mapping['item_id']
                 for prov in self.providers.values():
                     if prov.prov_id == prov_id:
-                        if action == 'add':
+                        if action == 'library_add':
                             result = await prov.add_library(prov_item_id, media_type)
-                        elif action == 'remove':
+                            await self.mass.db.add_to_library(item.item_id, item.media_type, prov_id)
+                        elif action == 'library_remove':
                             result = await prov.remove_library(prov_item_id, media_type)
+                            await self.mass.db.remove_from_library(item.item_id, item.media_type, prov_id)
         return result
     
     async def add_playlist_tracks(self, playlist_id, tracks:List[Track]):
@@ -239,7 +241,7 @@ class MusicManager():
             track_playlist_provs = [item['provider'] for item in track.provider_ids]
             if playlist_prov['provider'] in track_playlist_provs:
                 # a track can contain multiple versions on the same provider
-                # simply sort by quality and just add the first one (assuming the track is still available)
+                # simply sort by quality and just add the first one (assuming the track is still available)
                 track_versions = sorted(track.provider_ids, key=operator.itemgetter('quality'), reverse=True)
                 for track_version in track_versions:
                     if track_version['provider'] == playlist_prov['provider']:
index 10e740717f0c62ca80679a033ef24e08f808cb8c..506c596f7b7f0862c9bea597debe0d113fd1a81c 100644 (file)
@@ -252,6 +252,8 @@ class FileProvider(MusicProvider):
         ''' return the content details for the given track when it will be streamed'''
         if not os.sep in track_id:
             track_id = base64.b64decode(track_id).decode('utf-8')
+        if not os.path.isfile(track_id):
+            return None
         # TODO: retrieve sanple rate and bitdepth
         return {
             "type": "file",
index 0e2a266235cd0291953cdd0a56282ffdc93a662a..d51ab00ab508b72fe42158d1fe2d8fae50cafa4d 100644 (file)
@@ -223,6 +223,22 @@ class SpotifyProvider(MusicProvider):
         await self.mass.db.remove_from_library(item.item_id, media_type, self.prov_id)
         LOGGER.debug("deleted item %s from %s - %s" %(prov_item_id, self.prov_id, result))
 
+    async def add_playlist_tracks(self, prov_playlist_id, prov_track_ids):
+        ''' add track(s) to playlist '''
+        track_uris = []
+        for track_id in prov_track_ids:
+            track_uris.append("spotify:track:%s" % track_id)
+        data = {"uris": track_uris}
+        return await self.__post_data(f'playlists/{prov_playlist_id}/tracks', data=data)
+
+    async def remove_playlist_tracks(self, prov_playlist_id, prov_track_ids):
+        ''' remove track(s) from playlist '''
+        track_uris = []
+        for track_id in prov_track_ids:
+            track_uris.append("spotify:track:%s" % track_id)
+        data = {"tracks": track_uris}
+        return await self.__delete_data(f'playlists/{prov_playlist_id}/tracks', data=data)
+
     async def devices(self):
         ''' list all available devices '''
         items = await self.__get_data('me/player/devices')
@@ -245,16 +261,22 @@ class SpotifyProvider(MusicProvider):
 
     async def get_stream_details(self, track_id):
         ''' return the content details for the given track when it will be streamed'''
-        # make sure there is a valid token in cache
+        # make sure a valid track is requested
+        track = await self.get_track(track_id)
+        if not track:
+            return None
+        # make sure that the token is still valid by just requesting it
         await self.get_token()
         spotty = self.get_spotty_binary()
-        spotty_exec = '%s -n temp -c "%s" --pass-through --single-track %s' %(spotty, self.mass.datapath, track_id)
+        spotty_exec = '%s -n temp -c "%s" --pass-through --single-track %s' %(spotty, self.mass.datapath, track.item_id)
         return {
             "type": "executable",
             "path": spotty_exec,
             "content_type": "ogg",
             "sample_rate": 44100,
-            "bit_depth": 16
+            "bit_depth": 16,
+            "provider": PROV_ID,
+            "item_id": track.item_id
         }
         
     async def __parse_artist(self, artist_obj):
@@ -483,12 +505,12 @@ class SpotifyProvider(MusicProvider):
                     result = None
                 return result
 
-    async def __delete_data(self, endpoint, params={}):
-        ''' get data from api'''
+    async def __delete_data(self, endpoint, params={}, data=None):
+        ''' delete data from api'''
         url = 'https://api.spotify.com/v1/%s' % endpoint
         token = await self.get_token()
         headers = {'Authorization': 'Bearer %s' % token["accessToken"]}
-        async with self.http_session.delete(url, headers=headers, params=params) as response:
+        async with self.http_session.delete(url, headers=headers, params=params, json=data, verify_ssl=False) as response:
             return await response.text()
 
     async def __put_data(self, endpoint, params={}, data=None):
@@ -496,7 +518,15 @@ class SpotifyProvider(MusicProvider):
         url = 'https://api.spotify.com/v1/%s' % endpoint
         token = await self.get_token()
         headers = {'Authorization': 'Bearer %s' % token["accessToken"]}
-        async with self.http_session.put(url, headers=headers, params=params, json=data) as response:
+        async with self.http_session.put(url, headers=headers, params=params, json=data, verify_ssl=False) as response:
+            return await response.text()
+
+    async def __post_data(self, endpoint, params={}, data=None):
+        ''' post data on api'''
+        url = 'https://api.spotify.com/v1/%s' % endpoint
+        token = await self.get_token()
+        headers = {'Authorization': 'Bearer %s' % token["accessToken"]}
+        async with self.http_session.post(url, headers=headers, params=params, json=data, verify_ssl=False) as response:
             return await response.text()
 
     @staticmethod
index 61a4970b578834d7257887762c47ef19f10fabeb..c8fb27f93dee7f6d4083db37afb8592df9fd2bc7 100644 (file)
@@ -200,7 +200,7 @@ class PySqueezePlayer(Player):
     async def cmd_queue_insert(self, queue_items, insert_at_index):
         # queue handled by built-in queue controller
         # we only check the start index
-        if insert_at_index == 0:
+        if insert_at_index == self.queue.cur_index:
             return await self.cmd_queue_play_index(insert_at_index)
 
     async def cmd_queue_append(self, queue_items):
index ae8840cd561129e654f791cd58ddc9c7440e5b75..87f0d9a1d15cd029995a559a7df9efa9cf13ceba 100755 (executable)
@@ -82,9 +82,9 @@ class Web():
         app.add_routes([web.get('/api/artists/{artist_id}/toptracks', self.artist_toptracks)])
         app.add_routes([web.get('/api/artists/{artist_id}/albums', self.artist_albums)])
         app.add_routes([web.get('/api/albums/{album_id}/tracks', self.album_tracks)])
-        app.add_routes([web.get('/api/{media_type}', self.get_items)])
         app.add_routes([web.get('/api/{media_type}/{media_id}/{action}', self.get_item)])
         app.add_routes([web.get('/api/{media_type}/{media_id}', self.get_item)])
+        app.add_routes([web.get('/api/{media_type}', self.get_items)])
         app.add_routes([web.get('/', self.index)])
         webdir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'web/')
         app.router.add_static("/", webdir)
@@ -349,6 +349,12 @@ class Web():
             await player.next()
         elif cmd_str == 'playlist index -1':
             await player.previous()
+        elif 'mixer volume' in cmd_str and '+' in cmds[2]:
+            volume_level = cmds[2].split('+')[1]
+            await player.volume_set(volume_level)
+        elif 'mixer volume' in cmd_str and '-' in cmds[2]:
+            volume_level = cmds[2].split('-')[1]
+            await player.volume_set(volume_level)
         elif 'mixer volume' in cmd_str:
             await player.volume_set(cmds[2])
         elif cmd_str == 'mixer muting 1':