Update to latest Plex API, add music support. (#2739)

* Update to latest Plex API, add music support.

* Fix PyLint errors.

* Update Plex sensor module to latest PlexAPI.

* Oops - update Python sensor import.

* According to PlexAPI docs, this is the new API for Plex Pass members.

* More pylint STFUs.

* Move pylint suppression.

* Use plexapi NA type directly.

* Pylint objects to short variable names.
pull/2754/head
Dean Camera 2016-08-09 01:55:58 +10:00 committed by Paulus Schoutsen
parent 689939ab9d
commit 5ff9e59b79
4 changed files with 78 additions and 29 deletions

View File

@ -688,7 +688,11 @@ class MediaPlayerImageView(HomeAssistantView):
if not authenticated: if not authenticated:
return self.Response(status=401) return self.Response(status=401)
response = requests.get(player.media_image_url) image_url = player.media_image_url
if image_url:
response = requests.get(image_url)
else:
response = None
if response is None: if response is None:
return self.Response(status=500) return self.Response(status=500)

View File

@ -12,15 +12,16 @@ from urllib.parse import urlparse
import homeassistant.util as util import homeassistant.util as util
from homeassistant.components.media_player import ( from homeassistant.components.media_player import (
MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO, MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK,
SUPPORT_PREVIOUS_TRACK, MediaPlayerDevice) SUPPORT_PREVIOUS_TRACK, SUPPORT_PAUSE, SUPPORT_STOP, SUPPORT_VOLUME_SET,
MediaPlayerDevice)
from homeassistant.const import ( from homeassistant.const import (
DEVICE_DEFAULT_NAME, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING, DEVICE_DEFAULT_NAME, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING,
STATE_UNKNOWN) STATE_UNKNOWN)
from homeassistant.loader import get_component from homeassistant.loader import get_component
from homeassistant.helpers.event import (track_utc_time_change) from homeassistant.helpers.event import (track_utc_time_change)
REQUIREMENTS = ['plexapi==1.1.0'] REQUIREMENTS = ['plexapi==2.0.2']
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1) MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1)
@ -30,7 +31,8 @@ PLEX_CONFIG_FILE = 'plex.conf'
_CONFIGURING = {} _CONFIGURING = {}
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SUPPORT_PLEX = SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK SUPPORT_PLEX = SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \
SUPPORT_STOP | SUPPORT_VOLUME_SET
def config_from_file(filename, config=None): def config_from_file(filename, config=None):
@ -193,6 +195,9 @@ class PlexClient(MediaPlayerDevice):
# pylint: disable=too-many-public-methods, attribute-defined-outside-init # pylint: disable=too-many-public-methods, attribute-defined-outside-init
def __init__(self, device, plex_sessions, update_devices, update_sessions): def __init__(self, device, plex_sessions, update_devices, update_sessions):
"""Initialize the Plex device.""" """Initialize the Plex device."""
from plexapi.utils import NA
self.na_type = NA
self.plex_sessions = plex_sessions self.plex_sessions = plex_sessions
self.update_devices = update_devices self.update_devices = update_devices
self.update_sessions = update_sessions self.update_sessions = update_sessions
@ -211,20 +216,17 @@ class PlexClient(MediaPlayerDevice):
@property @property
def name(self): def name(self):
"""Return the name of the device.""" """Return the name of the device."""
return self.device.name or DEVICE_DEFAULT_NAME return self.device.title or DEVICE_DEFAULT_NAME
@property @property
def session(self): def session(self):
"""Return the session, if any.""" """Return the session, if any."""
if self.device.machineIdentifier not in self.plex_sessions: return self.plex_sessions.get(self.device.machineIdentifier, None)
return None
return self.plex_sessions[self.device.machineIdentifier]
@property @property
def state(self): def state(self):
"""Return the state of the device.""" """Return the state of the device."""
if self.session: if self.session and self.session.player:
state = self.session.player.state state = self.session.player.state
if state == 'playing': if state == 'playing':
return STATE_PLAYING return STATE_PLAYING
@ -243,11 +245,30 @@ class PlexClient(MediaPlayerDevice):
self.update_devices(no_throttle=True) self.update_devices(no_throttle=True)
self.update_sessions(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
else:
return value
@property
def _active_media_plexapi_type(self):
"""Get the active media type required by PlexAPI commands."""
if self.media_content_type is MEDIA_TYPE_MUSIC:
return 'music'
else:
return 'video'
@property @property
def media_content_id(self): def media_content_id(self):
"""Content ID of current playing media.""" """Content ID of current playing media."""
if self.session is not None: if self.session is not None:
return self.session.ratingKey return self._convert_na_to_none(self.session.ratingKey)
@property @property
def media_content_type(self): def media_content_type(self):
@ -259,65 +280,82 @@ class PlexClient(MediaPlayerDevice):
return MEDIA_TYPE_TVSHOW return MEDIA_TYPE_TVSHOW
elif media_type == 'movie': elif media_type == 'movie':
return MEDIA_TYPE_VIDEO return MEDIA_TYPE_VIDEO
elif media_type == 'track':
return MEDIA_TYPE_MUSIC
return None return None
@property @property
def media_duration(self): def media_duration(self):
"""Duration of current playing media in seconds.""" """Duration of current playing media in seconds."""
if self.session is not None: if self.session is not None:
return self.session.duration return self._convert_na_to_none(self.session.duration)
@property @property
def media_image_url(self): def media_image_url(self):
"""Image url of current playing media.""" """Image url of current playing media."""
if self.session is not None: if self.session is not None:
return self.session.thumbUrl thumb_url = self._convert_na_to_none(self.session.thumbUrl)
if str(self.na_type) in thumb_url:
# Audio tracks build their thumb urls internally before passing
# back a URL with the PlexAPI _NA type already converted to a
# string and embedded into a malformed URL
thumb_url = None
return thumb_url
@property @property
def media_title(self): def media_title(self):
"""Title of current playing media.""" """Title of current playing media."""
# find a string we can use as a title # find a string we can use as a title
if self.session is not None: if self.session is not None:
return self.session.title return self._convert_na_to_none(self.session.title)
@property @property
def media_season(self): def media_season(self):
"""Season of curent playing media (TV Show only).""" """Season of curent playing media (TV Show only)."""
from plexapi.video import Show from plexapi.video import Show
if isinstance(self.session, Show): if isinstance(self.session, Show):
return self.session.seasons()[0].index return self._convert_na_to_none(self.session.seasons()[0].index)
@property @property
def media_series_title(self): def media_series_title(self):
"""The title of the series of current playing media (TV Show only).""" """The title of the series of current playing media (TV Show only)."""
from plexapi.video import Show from plexapi.video import Show
if isinstance(self.session, Show): if isinstance(self.session, Show):
return self.session.grandparentTitle return self._convert_na_to_none(self.session.grandparentTitle)
@property @property
def media_episode(self): def media_episode(self):
"""Episode of current playing media (TV Show only).""" """Episode of current playing media (TV Show only)."""
from plexapi.video import Show from plexapi.video import Show
if isinstance(self.session, Show): if isinstance(self.session, Show):
return self.session.index return self._convert_na_to_none(self.session.index)
@property @property
def supported_media_commands(self): def supported_media_commands(self):
"""Flag of media commands that are supported.""" """Flag of media commands that are supported."""
return SUPPORT_PLEX return SUPPORT_PLEX
def set_volume_level(self, volume):
"""Set volume level, range 0..1."""
self.device.setVolume(int(volume * 100),
self._active_media_plexapi_type)
def media_play(self): def media_play(self):
"""Send play command.""" """Send play command."""
self.device.play() self.device.play(self._active_media_plexapi_type)
def media_pause(self): def media_pause(self):
"""Send pause command.""" """Send pause command."""
self.device.pause() self.device.pause(self._active_media_plexapi_type)
def media_stop(self):
"""Send stop command."""
self.device.stop(self._active_media_plexapi_type)
def media_next_track(self): def media_next_track(self):
"""Send next track command.""" """Send next track command."""
self.device.skipNext() self.device.skipNext(self._active_media_plexapi_type)
def media_previous_track(self): def media_previous_track(self):
"""Send previous track command.""" """Send previous track command."""
self.device.skipPrevious() self.device.skipPrevious(self._active_media_plexapi_type)

View File

@ -14,7 +14,7 @@ from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['plexapi==1.1.0'] REQUIREMENTS = ['plexapi==2.0.2']
CONF_SERVER = 'server' CONF_SERVER = 'server'
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1) MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1)
@ -54,15 +54,18 @@ class PlexSensor(Entity):
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
def __init__(self, name, plex_url, plex_user, plex_password, plex_server): def __init__(self, name, plex_url, plex_user, plex_password, plex_server):
"""Initialize the sensor.""" """Initialize the sensor."""
from plexapi.utils import NA
self._na_type = NA
self._name = name self._name = name
self._state = 0 self._state = 0
self._now_playing = [] self._now_playing = []
if plex_user and plex_password: if plex_user and plex_password:
from plexapi.myplex import MyPlexUser from plexapi.myplex import MyPlexAccount
user = MyPlexUser.signin(plex_user, plex_password) user = MyPlexAccount.signin(plex_user, plex_password)
server = plex_server if plex_server else user.resources()[0].name server = plex_server if plex_server else user.resources()[0].name
self._server = user.getResource(server).connect() self._server = user.resource(server).connect()
else: else:
from plexapi.server import PlexServer from plexapi.server import PlexServer
self._server = PlexServer(plex_url) self._server = PlexServer(plex_url)
@ -93,7 +96,11 @@ class PlexSensor(Entity):
def update(self): def update(self):
"""Update method for plex sensor.""" """Update method for plex sensor."""
sessions = self._server.sessions() sessions = self._server.sessions()
now_playing = [(s.user.title, "{0} ({1})".format(s.title, s.year)) now_playing = []
for s in sessions] for sess in sessions:
user = sess.user.title if sess.user 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 ""
now_playing.append((user, "{0} ({1})".format(title, year)))
self._state = len(sessions) self._state = len(sessions)
self._now_playing = now_playing self._now_playing = now_playing

View File

@ -228,7 +228,7 @@ phue==0.8
# homeassistant.components.media_player.plex # homeassistant.components.media_player.plex
# homeassistant.components.sensor.plex # homeassistant.components.sensor.plex
plexapi==1.1.0 plexapi==2.0.2
# homeassistant.components.sensor.serial_pm # homeassistant.components.sensor.serial_pm
pmsensor==0.2 pmsensor==0.2