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:
|
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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue