mirror of https://github.com/home-assistant/core
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
This commit is contained in:
parent
2a2a106e62
commit
41fa8cc8f2
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue