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
parent
689939ab9d
commit
5ff9e59b79
|
@ -688,7 +688,11 @@ class MediaPlayerImageView(HomeAssistantView):
|
|||
if not authenticated:
|
||||
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:
|
||||
return self.Response(status=500)
|
||||
|
|
|
@ -12,15 +12,16 @@ from urllib.parse import urlparse
|
|||
|
||||
import homeassistant.util as util
|
||||
from homeassistant.components.media_player import (
|
||||
MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
|
||||
SUPPORT_PREVIOUS_TRACK, MediaPlayerDevice)
|
||||
MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO, MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK,
|
||||
SUPPORT_PREVIOUS_TRACK, SUPPORT_PAUSE, SUPPORT_STOP, SUPPORT_VOLUME_SET,
|
||||
MediaPlayerDevice)
|
||||
from homeassistant.const import (
|
||||
DEVICE_DEFAULT_NAME, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING,
|
||||
STATE_UNKNOWN)
|
||||
from homeassistant.loader import get_component
|
||||
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_FORCED_SCANS = timedelta(seconds=1)
|
||||
|
||||
|
@ -30,7 +31,8 @@ PLEX_CONFIG_FILE = 'plex.conf'
|
|||
_CONFIGURING = {}
|
||||
_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):
|
||||
|
@ -193,6 +195,9 @@ class PlexClient(MediaPlayerDevice):
|
|||
# pylint: disable=too-many-public-methods, attribute-defined-outside-init
|
||||
def __init__(self, device, plex_sessions, update_devices, update_sessions):
|
||||
"""Initialize the Plex device."""
|
||||
from plexapi.utils import NA
|
||||
|
||||
self.na_type = NA
|
||||
self.plex_sessions = plex_sessions
|
||||
self.update_devices = update_devices
|
||||
self.update_sessions = update_sessions
|
||||
|
@ -211,20 +216,17 @@ class PlexClient(MediaPlayerDevice):
|
|||
@property
|
||||
def name(self):
|
||||
"""Return the name of the device."""
|
||||
return self.device.name or DEVICE_DEFAULT_NAME
|
||||
return self.device.title or DEVICE_DEFAULT_NAME
|
||||
|
||||
@property
|
||||
def session(self):
|
||||
"""Return the session, if any."""
|
||||
if self.device.machineIdentifier not in self.plex_sessions:
|
||||
return None
|
||||
|
||||
return self.plex_sessions[self.device.machineIdentifier]
|
||||
return self.plex_sessions.get(self.device.machineIdentifier, None)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the device."""
|
||||
if self.session:
|
||||
if self.session and self.session.player:
|
||||
state = self.session.player.state
|
||||
if state == 'playing':
|
||||
return STATE_PLAYING
|
||||
|
@ -243,11 +245,30 @@ 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
|
||||
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
|
||||
def media_content_id(self):
|
||||
"""Content ID of current playing media."""
|
||||
if self.session is not None:
|
||||
return self.session.ratingKey
|
||||
return self._convert_na_to_none(self.session.ratingKey)
|
||||
|
||||
@property
|
||||
def media_content_type(self):
|
||||
|
@ -259,65 +280,82 @@ class PlexClient(MediaPlayerDevice):
|
|||
return MEDIA_TYPE_TVSHOW
|
||||
elif media_type == 'movie':
|
||||
return MEDIA_TYPE_VIDEO
|
||||
elif media_type == 'track':
|
||||
return MEDIA_TYPE_MUSIC
|
||||
return None
|
||||
|
||||
@property
|
||||
def media_duration(self):
|
||||
"""Duration of current playing media in seconds."""
|
||||
if self.session is not None:
|
||||
return self.session.duration
|
||||
return self._convert_na_to_none(self.session.duration)
|
||||
|
||||
@property
|
||||
def media_image_url(self):
|
||||
"""Image url of current playing media."""
|
||||
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
|
||||
def media_title(self):
|
||||
"""Title of current playing media."""
|
||||
# find a string we can use as a title
|
||||
if self.session is not None:
|
||||
return self.session.title
|
||||
return self._convert_na_to_none(self.session.title)
|
||||
|
||||
@property
|
||||
def media_season(self):
|
||||
"""Season of curent playing media (TV Show only)."""
|
||||
from plexapi.video import Show
|
||||
if isinstance(self.session, Show):
|
||||
return self.session.seasons()[0].index
|
||||
return self._convert_na_to_none(self.session.seasons()[0].index)
|
||||
|
||||
@property
|
||||
def media_series_title(self):
|
||||
"""The title of the series of current playing media (TV Show only)."""
|
||||
from plexapi.video import Show
|
||||
if isinstance(self.session, Show):
|
||||
return self.session.grandparentTitle
|
||||
return self._convert_na_to_none(self.session.grandparentTitle)
|
||||
|
||||
@property
|
||||
def media_episode(self):
|
||||
"""Episode of current playing media (TV Show only)."""
|
||||
from plexapi.video import Show
|
||||
if isinstance(self.session, Show):
|
||||
return self.session.index
|
||||
return self._convert_na_to_none(self.session.index)
|
||||
|
||||
@property
|
||||
def supported_media_commands(self):
|
||||
"""Flag of media commands that are supported."""
|
||||
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):
|
||||
"""Send play command."""
|
||||
self.device.play()
|
||||
self.device.play(self._active_media_plexapi_type)
|
||||
|
||||
def media_pause(self):
|
||||
"""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):
|
||||
"""Send next track command."""
|
||||
self.device.skipNext()
|
||||
self.device.skipNext(self._active_media_plexapi_type)
|
||||
|
||||
def media_previous_track(self):
|
||||
"""Send previous track command."""
|
||||
self.device.skipPrevious()
|
||||
self.device.skipPrevious(self._active_media_plexapi_type)
|
||||
|
|
|
@ -14,7 +14,7 @@ from homeassistant.helpers.entity import Entity
|
|||
from homeassistant.util import Throttle
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['plexapi==1.1.0']
|
||||
REQUIREMENTS = ['plexapi==2.0.2']
|
||||
|
||||
CONF_SERVER = 'server'
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1)
|
||||
|
@ -54,15 +54,18 @@ class PlexSensor(Entity):
|
|||
# pylint: disable=too-many-arguments
|
||||
def __init__(self, name, plex_url, plex_user, plex_password, plex_server):
|
||||
"""Initialize the sensor."""
|
||||
from plexapi.utils import NA
|
||||
|
||||
self._na_type = NA
|
||||
self._name = name
|
||||
self._state = 0
|
||||
self._now_playing = []
|
||||
|
||||
if plex_user and plex_password:
|
||||
from plexapi.myplex import MyPlexUser
|
||||
user = MyPlexUser.signin(plex_user, plex_password)
|
||||
from plexapi.myplex import MyPlexAccount
|
||||
user = MyPlexAccount.signin(plex_user, plex_password)
|
||||
server = plex_server if plex_server else user.resources()[0].name
|
||||
self._server = user.getResource(server).connect()
|
||||
self._server = user.resource(server).connect()
|
||||
else:
|
||||
from plexapi.server import PlexServer
|
||||
self._server = PlexServer(plex_url)
|
||||
|
@ -93,7 +96,11 @@ class PlexSensor(Entity):
|
|||
def update(self):
|
||||
"""Update method for plex sensor."""
|
||||
sessions = self._server.sessions()
|
||||
now_playing = [(s.user.title, "{0} ({1})".format(s.title, s.year))
|
||||
for s in sessions]
|
||||
now_playing = []
|
||||
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._now_playing = now_playing
|
||||
|
|
|
@ -228,7 +228,7 @@ phue==0.8
|
|||
|
||||
# homeassistant.components.media_player.plex
|
||||
# homeassistant.components.sensor.plex
|
||||
plexapi==1.1.0
|
||||
plexapi==2.0.2
|
||||
|
||||
# homeassistant.components.sensor.serial_pm
|
||||
pmsensor==0.2
|
||||
|
|
Loading…
Reference in New Issue