-
-
Notifications
You must be signed in to change notification settings - Fork 31.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Spotify: Play owned/starred playlists by name/by frontend #11887
Closed
Closed
Changes from 16 commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
dac3520
Merge remote-tracking branch 'refs/remotes/home-assistant/dev' into dev
tringler d9cc0b4
Merge remote-tracking branch 'refs/remotes/home-assistant/dev' into dev
tringler a667e4b
Changed requirement to original spotipy repo
tringler 618e751
Updated requirements
tringler aec8885
Spotify: Play owned/starred playlist by name/uri
tringler d846753
Logging improvements (PEP 3101)
tringler b31bfb5
Removed unused key
tringler 9160610
Revoked PEP 3101 annotation
tringler caabeb9
Updated requirements by script
tringler 2c591fd
Merge branch 'spotify-playlists' of https://github.com/tringler/home-…
tringler cb156ed
Logging bugfix
tringler 56bf578
Fixed pylint error: logging-not-lazy
tringler 50d061d
Fixed Flake8 error D204
tringler 287ad24
Fixed logging syntax error
tringler 5061856
Simplified file handling
tringler c780090
Removed useless load operations
tringler f7b6eb0
Fixed typo on def save()
tringler f10364d
Fixed another typo on def save()
tringler File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,8 @@ | |
""" | ||
import logging | ||
from datetime import timedelta | ||
import json | ||
import os | ||
|
||
import voluptuous as vol | ||
|
||
|
@@ -20,9 +22,8 @@ | |
CONF_NAME, STATE_PLAYING, STATE_PAUSED, STATE_IDLE, STATE_UNKNOWN) | ||
import homeassistant.helpers.config_validation as cv | ||
|
||
COMMIT = '544614f4b1d508201d363e84e871f86c90aa26b2' | ||
REQUIREMENTS = ['https://github.com/happyleavesaoc/spotipy/' | ||
'archive/%s.zip#spotipy==2.4.4' % COMMIT] | ||
REQUIREMENTS = ['https://github.com/plamere/spotipy/' | ||
'archive/master.zip#spotipy==2.4.4'] | ||
|
||
DEPENDENCIES = ['http'] | ||
|
||
|
@@ -32,7 +33,8 @@ | |
SUPPORT_NEXT_TRACK | SUPPORT_PREVIOUS_TRACK | SUPPORT_SELECT_SOURCE |\ | ||
SUPPORT_PLAY_MEDIA | SUPPORT_SHUFFLE_SET | ||
|
||
SCOPE = 'user-read-playback-state user-modify-playback-state user-read-private' | ||
SCOPE = 'user-read-playback-state user-modify-playback-state \ | ||
user-read-private playlist-read-private' | ||
DEFAULT_CACHE_PATH = '.spotify-token-cache' | ||
AUTH_CALLBACK_PATH = '/api/spotify' | ||
AUTH_CALLBACK_NAME = 'api:spotify' | ||
|
@@ -57,6 +59,7 @@ | |
}) | ||
|
||
SCAN_INTERVAL = timedelta(seconds=30) | ||
PERSISTENCE = '.spotify_playlists.json' | ||
|
||
|
||
def request_configuration(hass, config, add_devices, oauth): | ||
|
@@ -90,8 +93,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): | |
configurator = hass.components.configurator | ||
configurator.request_done(hass.data.get(DOMAIN)) | ||
del hass.data[DOMAIN] | ||
playlistdata = hass.data[DOMAIN] = PlaylistData(hass) | ||
hass.http.register_view(SpotifyPlaylistView) | ||
player = SpotifyMediaPlayer(oauth, config.get(CONF_NAME, DEFAULT_NAME), | ||
config[CONF_ALIASES]) | ||
config[CONF_ALIASES], playlistdata) | ||
add_devices([player], True) | ||
|
||
|
||
|
@@ -119,7 +124,7 @@ def get(self, request): | |
class SpotifyMediaPlayer(MediaPlayerDevice): | ||
"""Representation of a Spotify controller.""" | ||
|
||
def __init__(self, oauth, name, aliases): | ||
def __init__(self, oauth, name, aliases, playlistdata): | ||
"""Initialize.""" | ||
self._name = name | ||
self._oauth = oauth | ||
|
@@ -137,6 +142,7 @@ def __init__(self, oauth, name, aliases): | |
self._user = None | ||
self._aliases = aliases | ||
self._token_info = self._oauth.get_cached_token() | ||
self._playlistdata = playlistdata | ||
|
||
def refresh_spotify_instance(self): | ||
"""Fetch a new spotify instance.""" | ||
|
@@ -210,6 +216,37 @@ def update(self): | |
self._volume = device.get('volume_percent') / 100 | ||
if device.get('name'): | ||
self._current_device = device.get('name') | ||
# Discover playlists | ||
discovered_playlists = self._player.current_user_playlists() | ||
if discovered_playlists is not None: | ||
new_playlists = {} | ||
for discovered_playlist in discovered_playlists['items']: | ||
playlist = { | ||
'name': discovered_playlist['name'], | ||
'id': discovered_playlist['id'], | ||
'collaborative': discovered_playlist['collaborative'], | ||
'public': discovered_playlist['public'], | ||
'owner': discovered_playlist['owner']['id'], | ||
'uri': discovered_playlist['uri'], | ||
'snapshot_id': discovered_playlist['snapshot_id'], | ||
'images': discovered_playlist['images'] | ||
} | ||
new_playlists[discovered_playlist['id']] = playlist | ||
if not (discovered_playlist['id'] | ||
in self._playlistdata.playlists): | ||
self._playlistdata.update(playlist) | ||
_LOGGER.info("New playlist discovered: %s (%s)", | ||
discovered_playlist['name'], | ||
discovered_playlist['uri']) | ||
playlist_diff = set(self._playlistdata.playlists.keys()) - \ | ||
set(new_playlists.keys()) | ||
if playlist_diff is not None: | ||
for removed_playlist in playlist_diff: | ||
_LOGGER.info("Playlist removed: %s (%s)", | ||
removed_playlist['name'], | ||
removed_playlist['uri']) | ||
del self._playlistdata.playlists[removed_playlist] | ||
self._playlistdata.update() | ||
|
||
def set_volume_level(self, volume): | ||
"""Set the volume level.""" | ||
|
@@ -247,12 +284,15 @@ def play_media(self, media_type, media_id, **kwargs): | |
if media_type == MEDIA_TYPE_MUSIC: | ||
kwargs['uris'] = [media_id] | ||
elif media_type == MEDIA_TYPE_PLAYLIST: | ||
for playlist in self._playlistdata.playlists.values(): | ||
if media_id in str(playlist): | ||
media_id = playlist["uri"] | ||
kwargs['context_uri'] = media_id | ||
else: | ||
_LOGGER.error("media type %s is not supported", media_type) | ||
return | ||
if not media_id.startswith('spotify:'): | ||
_LOGGER.error("media id must be spotify uri") | ||
_LOGGER.error("media id %s is not a valid spotify uri", media_id) | ||
return | ||
self._player.start_playback(**kwargs) | ||
|
||
|
@@ -328,3 +368,43 @@ def supported_features(self): | |
def media_content_type(self): | ||
"""Return the media type.""" | ||
return MEDIA_TYPE_MUSIC | ||
|
||
|
||
class SpotifyPlaylistView(HomeAssistantView): | ||
"""View to retrieve Spotify Playlists.""" | ||
|
||
url = '/api/spotify/playlists' | ||
name = "api:spotify/playlists" | ||
|
||
@callback | ||
def get(self, request): | ||
"""Retrieve playlists.""" | ||
return self.json(request.app['hass'].data[DOMAIN].playlists) | ||
|
||
|
||
class PlaylistData: | ||
"""Class to hold Spotify playlist data.""" | ||
|
||
def __init__(self, hass): | ||
"""Initialize the Spotify playlist store list.""" | ||
self.hass = hass | ||
self.playlists = self.load() | ||
|
||
def update(self, playlist=None): | ||
"""Add a playlist.""" | ||
if playlist is not None: | ||
self.playlists[playlist['id']] = playlist | ||
self.save() | ||
|
||
def load(self): | ||
"""Load the playlists.""" | ||
path = self.hass.config.path(PERSISTENCE) | ||
if not os.path.isfile(path): | ||
return {} | ||
with open(path) as p_file: | ||
return json.loads(p_file) | ||
|
||
def save(self): | ||
"""Save playlists.""" | ||
with open(self.hass.config.path(PERSISTENCE), 'wt') as p_file: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. local variable 'p_file' is assigned to but never used |
||
json.dump(self.playlist, yp_file) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. undefined name 'yp_file' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just checked and spotipy is available in pypi in version 2.4.4
No need for tor this hack...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is needed, because the current PyPi 2.4.4 package does not include this PR: spotipy-dev/spotipy#182
The pypi package is from January 2017.
At least I wanted to upgrade from @happyleavesaoc repo to the original master repo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just download the code from pypi and GitHub
This package is 💩... same version and different content :(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, definitely track the
spotipy
release instead of my fork. w.r.t. pypi version, issue has been open for awhile: spotipy-dev/spotipy#211There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe contacting @plamere directly through email ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have the power to merge :)
But the guidelines are well defined: only pypi packages in requirements
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That means I cannot improve a component which is using a GitHub repo since years? Don't touch that component otherwise you are forced to persuade the pypi owner to update the package or maintain your own package for us.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reasoning is that the master.zip can change and break stuff, while pypi packages will perdure in time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great - Then I will revert to @happyleavesaoc commit .zip 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will leave it as it is for now - If someone wants to merge it, he can just revert the 618e751 and a667e4b