From 41fa8cc8f24761b75051926121a4fe474f46e0fa Mon Sep 17 00:00:00 2001 From: Ryan McLean Date: Wed, 25 Oct 2017 10:42:13 +0100 Subject: [PATCH] Plex refactor Part 1 - Update plexapi to 3.0.3 (#9988) * Fix: Last Played Media Title in plex would stay even when player was idle/off Primary Fix is in the "if self._device" portion. code in "if self._session" is a catch all but i'm not 100% if it is needed. * Fixed lint issues with previous commit * 1st Pass at refactoring plex refresh Moved _media** into clearMedia() which is called in _init_ and at start of refresh. Removed redunant _media_* = None entries Grouped TV Show and Music under single if rather than testing seperately for now. * Fixed invalid name for _clearMedia() Removed another media_* = None entry * Removed print() statements used for debug * Removed unneeded "if" statement * Changes * Updated Requests to 2.18.4 * Updated plexapi to 3.0.3 * Removed function to convert_NA_to_None * Removed function get_thumb_url Type changes * _session.player is now a list players * na_type deprecated and to be removed * plexapi has change na to None Known Issues: * Player controls currently broken * Last location (library) stays while player idle * Username is now Usernames and a list * Fix for broken controls * Removed errant print statement * Removed depecrated na_type * Updated Plex Sensor to use plexapi 3.0.3 Added support for Token to be used in sensor Known Issues: Username and Password broken for Plex Sensor use Token instead for now * removed TODOs * Fixes for private access violations * Removed need for _local_client_fix * Removed unused import and fixed parens --- homeassistant/components/media_player/plex.py | 168 +++++------------- homeassistant/components/sensor/plex.py | 26 +-- requirements_all.txt | 2 +- 3 files changed, 64 insertions(+), 132 deletions(-) diff --git a/homeassistant/components/media_player/plex.py b/homeassistant/components/media_player/plex.py index 2bf35666873..7d836fb59b7 100644 --- a/homeassistant/components/media_player/plex.py +++ b/homeassistant/components/media_player/plex.py @@ -8,7 +8,6 @@ import json import logging import os from datetime import timedelta -from urllib.parse import urlparse import requests import voluptuous as vol @@ -24,7 +23,7 @@ from homeassistant.const import ( from homeassistant.helpers import config_validation as cv from homeassistant.helpers.event import track_utc_time_change -REQUIREMENTS = ['plexapi==2.0.2'] +REQUIREMENTS = ['plexapi==3.0.3'] _CONFIGURING = {} _LOGGER = logging.getLogger(__name__) @@ -183,7 +182,7 @@ def setup_plexserver( if device.machineIdentifier not in plex_clients: new_client = PlexClient(config, device, None, plex_sessions, update_devices, - update_sessions) + update_sessions, plexserver) plex_clients[device.machineIdentifier] = new_client new_plex_clients.append(new_client) else: @@ -196,7 +195,7 @@ def setup_plexserver( and machine_identifier is not None): new_client = PlexClient(config, None, session, plex_sessions, update_devices, - update_sessions) + update_sessions, plexserver) plex_clients[machine_identifier] = new_client new_plex_clients.append(new_client) else: @@ -225,9 +224,8 @@ def setup_plexserver( plex_sessions.clear() for session in sessions: - if (session.player is not None and - session.player.machineIdentifier is not None): - plex_sessions[session.player.machineIdentifier] = session + for player in session.players: + plex_sessions[player.machineIdentifier] = session update_sessions() update_devices() @@ -277,14 +275,15 @@ class PlexClient(MediaPlayerDevice): """Representation of a Plex device.""" def __init__(self, config, device, session, plex_sessions, - update_devices, update_sessions): + update_devices, update_sessions, plex_server): """Initialize the Plex device.""" - from plexapi.utils import NA self._app_name = '' + self._server = plex_server self._device = None self._device_protocol_capabilities = None self._is_player_active = False self._is_player_available = False + self._player = None self._machine_identifier = None self._make = '' self._name = None @@ -296,7 +295,6 @@ class PlexClient(MediaPlayerDevice): self._state = STATE_IDLE self._volume_level = 1 # since we can't retrieve remotely self._volume_muted = False # since we can't retrieve remotely - self.na_type = NA self.config = config self.plex_sessions = plex_sessions self.update_devices = update_devices @@ -353,42 +351,35 @@ class PlexClient(MediaPlayerDevice): self._session = session if device: self._device = device + if "127.0.0.1" in self._device.url("/"): + self._device.proxyThroughServer() self._session = None - - if self._device: - self._machine_identifier = self._convert_na_to_none( - self._device.machineIdentifier) - self._name = self._convert_na_to_none( - self._device.title) or DEVICE_DEFAULT_NAME + self._machine_identifier = self._device.machineIdentifier + self._name = self._device.title or DEVICE_DEFAULT_NAME self._device_protocol_capabilities = ( self._device.protocolCapabilities) - # set valid session, preferring device session - if self._device and self.plex_sessions.get( - self._device.machineIdentifier, None): - self._session = self._convert_na_to_none(self.plex_sessions.get( - self._device.machineIdentifier, None)) + # set valid session, preferring device session + if self.plex_sessions.get(self._device.machineIdentifier, None): + self._session = self.plex_sessions.get( + self._device.machineIdentifier, None) if self._session: - self._media_position = self._convert_na_to_none( - self._session.viewOffset) - self._media_content_id = self._convert_na_to_none( - self._session.ratingKey) - self._media_content_rating = self._convert_na_to_none( - self._session.contentRating) - - # player dependent data - if self._session and self._session.player: - self._is_player_available = True - self._machine_identifier = self._convert_na_to_none( - self._session.player.machineIdentifier) - self._name = self._convert_na_to_none(self._session.player.title) - self._player_state = self._session.player.state - self._session_username = self._convert_na_to_none( - self._session.username) - self._make = self._convert_na_to_none(self._session.player.device) - else: - self._is_player_available = False + if self._device.machineIdentifier is not None and \ + self._session.players: + self._is_player_available = True + self._player = [p for p in self._session.players + if p.machineIdentifier == + self._device.machineIdentifier][0] + self._name = self._player.title + self._player_state = self._player.state + self._session_username = self._session.usernames[0] + self._make = self._player.device + else: + self._is_player_available = False + self._media_position = self._session.viewOffset + self._media_content_id = self._session.ratingKey + self._media_content_rating = self._session.contentRating if self._player_state == 'playing': self._is_player_active = True @@ -405,8 +396,7 @@ class PlexClient(MediaPlayerDevice): if self._is_player_active and self._session is not None: self._session_type = self._session.type - self._media_duration = self._convert_na_to_none( - self._session.duration) + self._media_duration = self._session.duration else: self._session_type = None @@ -424,40 +414,34 @@ class PlexClient(MediaPlayerDevice): # title (movie name, tv episode name, music song name) if self._session and self._is_player_active: - self._media_title = self._convert_na_to_none(self._session.title) + self._media_title = self._session.title # Movies if (self.media_content_type == MEDIA_TYPE_VIDEO and - self._convert_na_to_none(self._session.year) is not None): + self._session.year is not None): self._media_title += ' (' + str(self._session.year) + ')' # TV Show if self._media_content_type is MEDIA_TYPE_TVSHOW: # season number (00) - if callable(self._convert_na_to_none(self._session.seasons)): - self._media_season = self._convert_na_to_none( - self._session.seasons()[0].index).zfill(2) - elif self._convert_na_to_none( - self._session.parentIndex) is not None: + if callable(self._session.seasons): + self._media_season = self._session.seasons()[0].index.zfill(2) + elif self._session.parentIndex is not None: self._media_season = self._session.parentIndex.zfill(2) else: self._media_season = None # show name - self._media_series_title = self._convert_na_to_none( - self._session.grandparentTitle) + self._media_series_title = self._session.grandparentTitle # episode number (00) - if self._convert_na_to_none(self._session.index) is not None: + if self._session.index is not None: self._media_episode = str(self._session.index).zfill(2) # Music if self._media_content_type == MEDIA_TYPE_MUSIC: - self._media_album_name = self._convert_na_to_none( - self._session.parentTitle) - self._media_album_artist = self._convert_na_to_none( - self._session.grandparentTitle) - self._media_track = self._convert_na_to_none(self._session.index) - self._media_artist = self._convert_na_to_none( - self._session.originalTitle) + self._media_album_name = self._session.parentTitle + self._media_album_artist = self._session.grandparentTitle + self._media_track = self._session.index + self._media_artist = self._session.originalTitle # use album artist if track artist is missing if self._media_artist is None: _LOGGER.debug("Using album artist because track artist " @@ -466,41 +450,26 @@ class PlexClient(MediaPlayerDevice): # set app name to library name if (self._session is not None - and self._session.librarySectionID is not None): - self._app_name = self._convert_na_to_none( - self._session.server.library.sectionByID( - self._session.librarySectionID).title) + and self._session.section() is not None): + self._app_name = self._session.section().title else: self._app_name = '' # media image url if self._session is not None: - thumb_url = self._get_thumbnail_url(self._session.thumb) + thumb_url = self._session.thumbUrl if (self.media_content_type is MEDIA_TYPE_TVSHOW and not self.config.get(CONF_USE_EPISODE_ART)): - thumb_url = self._get_thumbnail_url( + thumb_url = self._server.url( self._session.grandparentThumb) if thumb_url is None: _LOGGER.debug("Using media art because media thumb " "was not found: %s", self.entity_id) - thumb_url = self._get_thumbnail_url(self._session.art) + thumb_url = self._server.url(self._session.art) self._media_image_url = thumb_url - def _get_thumbnail_url(self, property_value): - """Return full URL (if exists) for a thumbnail property.""" - if self._convert_na_to_none(property_value) is None: - return None - - if self._session is None or self._session.server is None: - return None - - url = self._session.server.url(property_value) - response = requests.get(url, verify=False) - if response and response.status_code == 200: - return url - def force_idle(self): """Force client to idle.""" self._state = STATE_IDLE @@ -548,17 +517,6 @@ class PlexClient(MediaPlayerDevice): self.update_devices(no_throttle=True) self.update_sessions(no_throttle=True) - # pylint: disable=no-self-use, singleton-comparison - def _convert_na_to_none(self, value): - """Convert PlexAPI _NA() instances to None.""" - # PlexAPI will return a "__NA__" object which can be compared to - # None, but isn't actually None - this converts it to a real None - # type so that lower layers don't think it's a URL and choke on it - if value is self.na_type: - return None - - return value - @property def _active_media_plexapi_type(self): """Get the active media type required by PlexAPI commands.""" @@ -685,32 +643,9 @@ class PlexClient(MediaPlayerDevice): return None - def _local_client_control_fix(self): - """Detect if local client and adjust url to allow control.""" - if self.device is None: - return - - # if this device's machineIdentifier matches an active client - # with a loopback address, the device must be local or casting - for client in self.device.server.clients(): - if ("127.0.0.1" in client.baseurl and - client.machineIdentifier == self.device.machineIdentifier): - # point controls to server since that's where the - # playback is occurring - _LOGGER.debug( - "Local client detected, redirecting controls to " - "Plex server: %s", self.entity_id) - server_url = self.device.server.baseurl - client_url = self.device.baseurl - self.device.baseurl = "{}://{}:{}".format( - urlparse(client_url).scheme, - urlparse(server_url).hostname, - str(urlparse(client_url).port)) - def set_volume_level(self, volume): """Set volume level, range 0..1.""" if self.device and 'playback' in self._device_protocol_capabilities: - self._local_client_control_fix() self.device.setVolume( int(volume * 100), self._active_media_plexapi_type) self._volume_level = volume # store since we can't retrieve @@ -749,19 +684,16 @@ class PlexClient(MediaPlayerDevice): def media_play(self): """Send play command.""" if self.device and 'playback' in self._device_protocol_capabilities: - self._local_client_control_fix() self.device.play(self._active_media_plexapi_type) def media_pause(self): """Send pause command.""" if self.device and 'playback' in self._device_protocol_capabilities: - self._local_client_control_fix() self.device.pause(self._active_media_plexapi_type) def media_stop(self): """Send stop command.""" if self.device and 'playback' in self._device_protocol_capabilities: - self._local_client_control_fix() self.device.stop(self._active_media_plexapi_type) def turn_off(self): @@ -772,13 +704,11 @@ class PlexClient(MediaPlayerDevice): def media_next_track(self): """Send next track command.""" if self.device and 'playback' in self._device_protocol_capabilities: - self._local_client_control_fix() self.device.skipNext(self._active_media_plexapi_type) def media_previous_track(self): """Send previous track command.""" if self.device and 'playback' in self._device_protocol_capabilities: - self._local_client_control_fix() self.device.skipPrevious(self._active_media_plexapi_type) # pylint: disable=W0613 @@ -874,8 +804,6 @@ class PlexClient(MediaPlayerDevice): if delete: media.delete() - self._local_client_control_fix() - server_url = self.device.server.baseurl.split(':') self.device.sendCommand('playback/playMedia', **dict({ 'machineIdentifier': self.device.server.machineIdentifier, diff --git a/homeassistant/components/sensor/plex.py b/homeassistant/components/sensor/plex.py index 1077c7462d2..0a75d0395ec 100644 --- a/homeassistant/components/sensor/plex.py +++ b/homeassistant/components/sensor/plex.py @@ -10,12 +10,12 @@ import voluptuous as vol from homeassistant.components.switch import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_HOST, CONF_PORT) + CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_HOST, CONF_PORT, CONF_TOKEN) from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['plexapi==2.0.2'] +REQUIREMENTS = ['plexapi==3.0.3'] _LOGGER = logging.getLogger(__name__) @@ -31,6 +31,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_PASSWORD): cv.string, + vol.Optional(CONF_TOKEN): cv.string, vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, vol.Optional(CONF_SERVER): cv.string, vol.Optional(CONF_USERNAME): cv.string, @@ -46,28 +47,31 @@ def setup_platform(hass, config, add_devices, discovery_info=None): plex_server = config.get(CONF_SERVER) plex_host = config.get(CONF_HOST) plex_port = config.get(CONF_PORT) + plex_token = config.get(CONF_TOKEN) plex_url = 'http://{}:{}'.format(plex_host, plex_port) add_devices([PlexSensor( - name, plex_url, plex_user, plex_password, plex_server)], True) + name, plex_url, plex_user, plex_password, plex_server, + plex_token)], True) class PlexSensor(Entity): """Representation of a Plex now playing sensor.""" - def __init__(self, name, plex_url, plex_user, plex_password, plex_server): + def __init__(self, name, plex_url, plex_user, plex_password, + plex_server, plex_token): """Initialize the sensor.""" - from plexapi.utils import NA from plexapi.myplex import MyPlexAccount from plexapi.server import PlexServer - self._na_type = NA self._name = name self._state = 0 self._now_playing = [] - if plex_user and plex_password: - user = MyPlexAccount.signin(plex_user, plex_password) + if plex_token: + self._server = PlexServer(plex_url, plex_token) + elif plex_user and plex_password: + user = MyPlexAccount(plex_user, plex_password) server = plex_server if plex_server else user.resources()[0].name self._server = user.resource(server).connect() else: @@ -99,9 +103,9 @@ class PlexSensor(Entity): sessions = self._server.sessions() now_playing = [] for sess in sessions: - user = sess.username if sess.username is not self._na_type else "" - title = sess.title if sess.title is not self._na_type else "" - year = sess.year if sess.year is not self._na_type else "" + user = sess.usernames[0] if sess.usernames is not None else "" + title = sess.title if sess.title is not None else "" + year = sess.year if sess.year is not None else "" now_playing.append((user, "{0} ({1})".format(title, year))) self._state = len(sessions) self._now_playing = now_playing diff --git a/requirements_all.txt b/requirements_all.txt index be3d415eda4..344374b51f6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -524,7 +524,7 @@ pilight==0.1.1 # homeassistant.components.media_player.plex # homeassistant.components.sensor.plex -plexapi==2.0.2 +plexapi==3.0.3 # homeassistant.components.sensor.mhz19 # homeassistant.components.sensor.serial_pm