Skip to content
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
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 87 additions & 7 deletions homeassistant/components/media_player/spotify.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"""
import logging
from datetime import timedelta
import json
import os

import voluptuous as vol

Expand All @@ -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']
Copy link
Contributor

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...

Copy link
Contributor Author

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.

Copy link
Contributor

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 :(

Copy link
Contributor

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#211

Copy link
Contributor

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 ?

Copy link
Contributor

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

Copy link
Contributor Author

@tringler tringler Feb 19, 2018

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.

Copy link
Contributor

@dgomes dgomes Feb 19, 2018

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.

Copy link
Contributor Author

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 👍

Copy link
Contributor Author

@tringler tringler Feb 19, 2018

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


DEPENDENCIES = ['http']

Expand All @@ -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'
Expand All @@ -57,6 +59,7 @@
})

SCAN_INTERVAL = timedelta(seconds=30)
PERSISTENCE = '.spotify_playlists.json'


def request_configuration(hass, config, add_devices, oauth):
Expand Down Expand Up @@ -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)


Expand Down Expand Up @@ -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
Expand All @@ -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."""
Expand Down Expand Up @@ -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."""
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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:

Choose a reason for hiding this comment

The 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)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

undefined name 'yp_file'

6 changes: 3 additions & 3 deletions requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -351,9 +351,6 @@ httplib2==0.10.3
# homeassistant.components.media_player.braviatv
https://github.com/aparraga/braviarc/archive/0.3.7.zip#braviarc==0.3.7

# homeassistant.components.media_player.spotify
https://github.com/happyleavesaoc/spotipy/archive/544614f4b1d508201d363e84e871f86c90aa26b2.zip#spotipy==2.4.4

# homeassistant.components.netatmo
https://github.com/jabesq/netatmo-api-python/archive/v0.9.2.1.zip#lnetatmo==0.9.2.1

Expand All @@ -366,6 +363,9 @@ https://github.com/jamespcole/home-assistant-nzb-clients/archive/616cad591540925
# homeassistant.components.switch.anel_pwrctrl
https://github.com/mweinelt/anel-pwrctrl/archive/ed26e8830e28a2bfa4260a9002db23ce3e7e63d7.zip#anel_pwrctrl==0.0.1

# homeassistant.components.media_player.spotify
https://github.com/plamere/spotipy/archive/master.zip#spotipy==2.4.4

# homeassistant.components.switch.edimax
https://github.com/rkabadi/pyedimax/archive/365301ce3ff26129a7910c501ead09ea625f3700.zip#pyedimax==0.1

Expand Down