From a24b38aacca23631fb2b625a26b992cd9124104e Mon Sep 17 00:00:00 2001 From: miniconfig Date: Sat, 19 Sep 2015 13:48:45 -0400 Subject: [PATCH 1/9] Initial version of plex media player component --- homeassistant/components/media_player/plex.py | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 homeassistant/components/media_player/plex.py diff --git a/homeassistant/components/media_player/plex.py b/homeassistant/components/media_player/plex.py new file mode 100644 index 00000000000..96d0b2d0f84 --- /dev/null +++ b/homeassistant/components/media_player/plex.py @@ -0,0 +1,188 @@ +""" +homeassistant.components.media_player.plex +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Provides an interface to the Plex API + +Configuration: + +To use Plex add something like this to your configuration: + +media_player: + platform: plex + name: plex + user: plex + password: my_secure_password + +Variables: + +name +*Required +The name of the backend device + +device +*Required +The frontend device (Look under settings > devices). + +user +*Required +The Plex username + +password +*Required +The Plex password +""" + +import logging + +from homeassistant.components.media_player import ( + MediaPlayerDevice, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, + SUPPORT_NEXT_TRACK, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO) +from homeassistant.const import ( + STATE_IDLE, STATE_PLAYING, STATE_PAUSED, STATE_UNKNOWN) +from plexapi.myplex import MyPlexUser + +_LOGGER = logging.getLogger(__name__) + +SUPPORT_PLEX = SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK + +# pylint: disable=abstract-method +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Sets up the plex platform. """ + name = config.get('name', '') + user = config.get('user', '') + password = config.get('password', '') + plexuser = MyPlexUser.signin(user, password) + plexserver = plexuser.getResource(name).connect() + dev = plexserver.clients() + for device in dev: + if not "PlayStation" in device.name: + add_devices([PlexClient(device.name, plexserver)]) + +class PlexClient(MediaPlayerDevice): + """ Represents a Plex device. """ + + # pylint: disable=too-many-public-methods + + + def __init__(self, name, plexserver): + self.client = plexserver.client(name) + self._name = name + self._media = None + self.update() + self.server = plexserver + + @property + def name(self): + """ Returns the name of the device. """ + return self._name + + @property + def state(self): + """ Returns the state of the device. """ + if self._media is None: + return STATE_IDLE + else: + state = self._media.get('state') + if state == 'playing': + return STATE_PLAYING + elif state == 'paused': + return STATE_PAUSED + else: + return STATE_UNKNOWN + + def update(self): + timeline = self.client.timeline() + for timeline_item in timeline: + if timeline_item.get('state') in ('playing', 'paused'): + self._media = timeline_item + + @property + def media_content_id(self): + """ Content ID of current playing media. """ + if self._media is not None: + return self._media.get('ratingKey') + + @property + def media_content_type(self): + """ Content type of current playing media. """ + if self._media is None: + return None + else: + media_type = self.server.library.getByKey(self.media_content_id).type + if media_type == 'episode': + return MEDIA_TYPE_TVSHOW + elif media_type == 'movie': + return MEDIA_TYPE_VIDEO + return None + + @property + def media_duration(self): + """ Duration of current playing media in seconds. """ + if self._media is not None: + total_time = self._media.get('duration') + + return total_time + + @property + def media_image_url(self): + """ Image url of current playing media. """ + if self._media is not None: + return self.server.library.getByKey(self.media_content_id).thumbUrl + else: + return None + + @property + def media_title(self): + """ Title of current playing media. """ + # find a string we can use as a title + if self._media is not None: + return self.server.library.getByKey(self.media_content_id).title + @property + def media_season(self): + """ Season of curent playing media. (TV Show only) """ + if self._media is not None: + show_season = self.server.library.getByKey(self.media_content_id).season().index + return show_season + else: + return None + + @property + def media_series_title(self): + """ Series title of current playing media. (TV Show only)""" + if self._media is not None: + series_title = self.server.library.getByKey(self.media_content_id).show().title + return series_title + else: + return None + + @property + def media_episode(self): + """ Episode of current playing media. (TV Show only) """ + if self._media is not None: + show_episode = self.server.library.getByKey(self.media_content_id).index + return show_episode + else: + return None + + @property + def supported_media_commands(self): + """ Flags of media commands that are supported. """ + return SUPPORT_PLEX + + def media_play(self): + """ media_play media player. """ + self.client.play() + + def media_pause(self): + """ media_pause media player. """ + self.client.pause() + + def media_next_track(self): + """ Send next track command. """ + self.client.skipNext() + + def media_previous_track(self): + """ Send previous track command. """ + self.client.skipPrevious() From 64741a95b86a2118a6b3d52828423158def1cfc5 Mon Sep 17 00:00:00 2001 From: miniconfig Date: Sat, 19 Sep 2015 14:16:57 -0400 Subject: [PATCH 2/9] Added requirements --- homeassistant/components/media_player/plex.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/homeassistant/components/media_player/plex.py b/homeassistant/components/media_player/plex.py index 96d0b2d0f84..358f845f4ae 100644 --- a/homeassistant/components/media_player/plex.py +++ b/homeassistant/components/media_player/plex.py @@ -42,6 +42,10 @@ from homeassistant.const import ( STATE_IDLE, STATE_PLAYING, STATE_PAUSED, STATE_UNKNOWN) from plexapi.myplex import MyPlexUser +REQUIREMENTS = ['https://github.com/miniconfig/python-plex-api/archive' + '437e36dca3b7780dc0cb73941d662302c0cd2fa9' + '#python-plexapi==1.0.2.5'] + _LOGGER = logging.getLogger(__name__) SUPPORT_PLEX = SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK From 48306ddbf671640a94e0065cf4b09916fb5496b9 Mon Sep 17 00:00:00 2001 From: miniconfig Date: Sun, 20 Sep 2015 08:19:21 -0400 Subject: [PATCH 3/9] Fixed Requirements URL --- homeassistant/components/media_player/plex.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_player/plex.py b/homeassistant/components/media_player/plex.py index 358f845f4ae..053825f3039 100644 --- a/homeassistant/components/media_player/plex.py +++ b/homeassistant/components/media_player/plex.py @@ -42,8 +42,8 @@ from homeassistant.const import ( STATE_IDLE, STATE_PLAYING, STATE_PAUSED, STATE_UNKNOWN) from plexapi.myplex import MyPlexUser -REQUIREMENTS = ['https://github.com/miniconfig/python-plex-api/archive' - '437e36dca3b7780dc0cb73941d662302c0cd2fa9' +REQUIREMENTS = ['https://github.com/miniconfig/python-plexapi/archive/' + '437e36dca3b7780dc0cb73941d662302c0cd2fa9.zip' '#python-plexapi==1.0.2.5'] _LOGGER = logging.getLogger(__name__) From 5027acfda18a7466fe6dc6dcab615239016d0de7 Mon Sep 17 00:00:00 2001 From: miniconfig Date: Sun, 20 Sep 2015 16:13:26 -0400 Subject: [PATCH 4/9] Fixed additional pylint and flake issues --- homeassistant/components/media_player/plex.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/media_player/plex.py b/homeassistant/components/media_player/plex.py index 053825f3039..f5a8106da5d 100644 --- a/homeassistant/components/media_player/plex.py +++ b/homeassistant/components/media_player/plex.py @@ -52,6 +52,8 @@ SUPPORT_PLEX = SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK # pylint: disable=abstract-method # pylint: disable=unused-argument + + def setup_platform(hass, config, add_devices, discovery_info=None): """ Sets up the plex platform. """ name = config.get('name', '') @@ -61,15 +63,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None): plexserver = plexuser.getResource(name).connect() dev = plexserver.clients() for device in dev: - if not "PlayStation" in device.name: + if "PlayStation" not in device.name: add_devices([PlexClient(device.name, plexserver)]) + class PlexClient(MediaPlayerDevice): """ Represents a Plex device. """ # pylint: disable=too-many-public-methods - def __init__(self, name, plexserver): self.client = plexserver.client(name) self._name = name @@ -114,7 +116,8 @@ class PlexClient(MediaPlayerDevice): if self._media is None: return None else: - media_type = self.server.library.getByKey(self.media_content_id).type + media_type = self.server.library.getByKey( + self.media_content_id).type if media_type == 'episode': return MEDIA_TYPE_TVSHOW elif media_type == 'movie': @@ -143,11 +146,13 @@ class PlexClient(MediaPlayerDevice): # find a string we can use as a title if self._media is not None: return self.server.library.getByKey(self.media_content_id).title + @property def media_season(self): """ Season of curent playing media. (TV Show only) """ if self._media is not None: - show_season = self.server.library.getByKey(self.media_content_id).season().index + show_season = self.server.library.getByKey( + self.media_content_id).season().index return show_season else: return None @@ -156,7 +161,8 @@ class PlexClient(MediaPlayerDevice): def media_series_title(self): """ Series title of current playing media. (TV Show only)""" if self._media is not None: - series_title = self.server.library.getByKey(self.media_content_id).show().title + series_title = self.server.library.getByKey( + self.media_content_id).show().title return series_title else: return None @@ -165,7 +171,8 @@ class PlexClient(MediaPlayerDevice): def media_episode(self): """ Episode of current playing media. (TV Show only) """ if self._media is not None: - show_episode = self.server.library.getByKey(self.media_content_id).index + show_episode = self.server.library.getByKey( + self.media_content_id).index return show_episode else: return None From a8e0ca6d3feac30280b22b5c5466909032d70e6e Mon Sep 17 00:00:00 2001 From: miniconfig Date: Mon, 21 Sep 2015 10:44:24 -0400 Subject: [PATCH 5/9] Fixed various property methods to make sure they all had a fall through return and removed unnecessary "else" statements --- homeassistant/components/media_player/plex.py | 31 +++++++------------ 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/media_player/plex.py b/homeassistant/components/media_player/plex.py index f5a8106da5d..cbc378df24a 100644 --- a/homeassistant/components/media_player/plex.py +++ b/homeassistant/components/media_player/plex.py @@ -95,8 +95,7 @@ class PlexClient(MediaPlayerDevice): return STATE_PLAYING elif state == 'paused': return STATE_PAUSED - else: - return STATE_UNKNOWN + return STATE_UNKNOWN def update(self): timeline = self.client.timeline() @@ -115,21 +114,19 @@ class PlexClient(MediaPlayerDevice): """ Content type of current playing media. """ if self._media is None: return None - else: - media_type = self.server.library.getByKey( - self.media_content_id).type - if media_type == 'episode': - return MEDIA_TYPE_TVSHOW - elif media_type == 'movie': - return MEDIA_TYPE_VIDEO - return None + media_type = self.server.library.getByKey( + self.media_content_id).type + if media_type == 'episode': + return MEDIA_TYPE_TVSHOW + elif media_type == 'movie': + return MEDIA_TYPE_VIDEO + return None @property def media_duration(self): """ Duration of current playing media in seconds. """ if self._media is not None: total_time = self._media.get('duration') - return total_time @property @@ -137,8 +134,7 @@ class PlexClient(MediaPlayerDevice): """ Image url of current playing media. """ if self._media is not None: return self.server.library.getByKey(self.media_content_id).thumbUrl - else: - return None + return None @property def media_title(self): @@ -154,8 +150,7 @@ class PlexClient(MediaPlayerDevice): show_season = self.server.library.getByKey( self.media_content_id).season().index return show_season - else: - return None + return None @property def media_series_title(self): @@ -164,8 +159,7 @@ class PlexClient(MediaPlayerDevice): series_title = self.server.library.getByKey( self.media_content_id).show().title return series_title - else: - return None + return None @property def media_episode(self): @@ -174,8 +168,7 @@ class PlexClient(MediaPlayerDevice): show_episode = self.server.library.getByKey( self.media_content_id).index return show_episode - else: - return None + return None @property def supported_media_commands(self): From d267f0a04c7f18020fdbf9ff3b2e4bf47336a03f Mon Sep 17 00:00:00 2001 From: miniconfig Date: Mon, 21 Sep 2015 10:59:34 -0400 Subject: [PATCH 6/9] Removed references to the frontend device parameter in the directions and added some clarification. Fixed plexapi version number. --- homeassistant/components/media_player/plex.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/media_player/plex.py b/homeassistant/components/media_player/plex.py index cbc378df24a..c05accc0042 100644 --- a/homeassistant/components/media_player/plex.py +++ b/homeassistant/components/media_player/plex.py @@ -10,7 +10,7 @@ To use Plex add something like this to your configuration: media_player: platform: plex - name: plex + name: plex_server user: plex password: my_secure_password @@ -18,11 +18,7 @@ Variables: name *Required -The name of the backend device - -device -*Required -The frontend device (Look under settings > devices). +The name of the backend device (Look under Plex Media Server > settings > server). user *Required @@ -44,7 +40,7 @@ from plexapi.myplex import MyPlexUser REQUIREMENTS = ['https://github.com/miniconfig/python-plexapi/archive/' '437e36dca3b7780dc0cb73941d662302c0cd2fa9.zip' - '#python-plexapi==1.0.2.5'] + '#python-plexapi==1.0.2'] _LOGGER = logging.getLogger(__name__) From cc7784889a6fbb5dcd6163a840cda8c6fcdec0da Mon Sep 17 00:00:00 2001 From: miniconfig Date: Mon, 21 Sep 2015 11:11:38 -0400 Subject: [PATCH 7/9] Pylint errors --- homeassistant/components/media_player/plex.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/media_player/plex.py b/homeassistant/components/media_player/plex.py index c05accc0042..3b7408cb2dc 100644 --- a/homeassistant/components/media_player/plex.py +++ b/homeassistant/components/media_player/plex.py @@ -18,7 +18,7 @@ Variables: name *Required -The name of the backend device (Look under Plex Media Server > settings > server). +The name of the backend device (Under Plex Media Server > settings > server). user *Required From 16d75b298110b8f15eee8b8af3060a62b41a192c Mon Sep 17 00:00:00 2001 From: miniconfig Date: Mon, 21 Sep 2015 11:45:52 -0400 Subject: [PATCH 8/9] Added plexapi library to requirements_all.txt --- requirements_all.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/requirements_all.txt b/requirements_all.txt index 19cc04016a8..bc25279fe43 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -133,3 +133,6 @@ https://github.com/balloob/home-assistant-vera-api/archive/a8f823066ead6c7da6fb5 # Sonos bindings (media_player.sonos) SoCo==0.11.1 + +# PlexAPI (media_player.plex) +https://github.com/miniconfig/python-plexapi/archive/437e36dca3b7780dc0cb73941d662302c0cd2fa9.zip#python-plexapi==1.0.2 From 03e7281406cbd938a302e5d3a288655db581ce1a Mon Sep 17 00:00:00 2001 From: miniconfig Date: Mon, 21 Sep 2015 11:59:55 -0400 Subject: [PATCH 9/9] Moved plexapi import into setup_platform(). Changed CONTRIBUTING.md to refer to requirements_all.txt instead of requirements.txt --- CONTRIBUTING.md | 2 +- homeassistant/components/media_player/plex.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3f2fd110a1d..f646766a231 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,7 +18,7 @@ For help on building your component, please see the [developer documentation](ht After you finish adding support for your device: - Update the supported devices in the `README.md` file. - - Add any new dependencies to `requirements.txt`. + - Add any new dependencies to `requirements_all.txt`. There is no ordering right now, so just add it to the end. - Update the `.coveragerc` file. - Provide some documentation for [home-assistant.io](https://home-assistant.io/). The documentation is handled in a separate [git repository](https://github.com/balloob/home-assistant.io). - Make sure all your code passes Pylint and flake8 (PEP8 and some more) validation. To generate reports, run `pylint homeassistant > pylint.txt` and `flake8 homeassistant --exclude bower_components,external > flake8.txt`. diff --git a/homeassistant/components/media_player/plex.py b/homeassistant/components/media_player/plex.py index 3b7408cb2dc..a43916f10e3 100644 --- a/homeassistant/components/media_player/plex.py +++ b/homeassistant/components/media_player/plex.py @@ -36,7 +36,6 @@ from homeassistant.components.media_player import ( SUPPORT_NEXT_TRACK, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO) from homeassistant.const import ( STATE_IDLE, STATE_PLAYING, STATE_PAUSED, STATE_UNKNOWN) -from plexapi.myplex import MyPlexUser REQUIREMENTS = ['https://github.com/miniconfig/python-plexapi/archive/' '437e36dca3b7780dc0cb73941d662302c0cd2fa9.zip' @@ -52,6 +51,7 @@ SUPPORT_PLEX = SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK def setup_platform(hass, config, add_devices, discovery_info=None): """ Sets up the plex platform. """ + from plexapi.myplex import MyPlexUser name = config.get('name', '') user = config.get('user', '') password = config.get('password', '')