2015-09-19 17:48:45 +00:00
|
|
|
"""
|
|
|
|
homeassistant.components.media_player.plex
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
Provides an interface to the Plex API
|
|
|
|
|
|
|
|
Configuration:
|
|
|
|
|
|
|
|
To use Plex add something like this to your configuration:
|
|
|
|
|
|
|
|
media_player:
|
|
|
|
platform: plex
|
2015-09-21 14:59:34 +00:00
|
|
|
name: plex_server
|
2015-09-19 17:48:45 +00:00
|
|
|
user: plex
|
|
|
|
password: my_secure_password
|
|
|
|
|
|
|
|
Variables:
|
|
|
|
|
|
|
|
name
|
|
|
|
*Required
|
2015-09-21 15:11:38 +00:00
|
|
|
The name of the backend device (Under Plex Media Server > settings > server).
|
2015-09-19 17:48:45 +00:00
|
|
|
|
|
|
|
user
|
|
|
|
*Required
|
|
|
|
The Plex username
|
|
|
|
|
|
|
|
password
|
|
|
|
*Required
|
|
|
|
The Plex password
|
|
|
|
"""
|
|
|
|
|
|
|
|
import logging
|
2015-09-29 19:50:07 +00:00
|
|
|
from datetime import timedelta
|
2015-09-19 17:48:45 +00:00
|
|
|
|
|
|
|
from homeassistant.components.media_player import (
|
|
|
|
MediaPlayerDevice, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK,
|
|
|
|
SUPPORT_NEXT_TRACK, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO)
|
|
|
|
from homeassistant.const import (
|
2015-09-29 19:50:07 +00:00
|
|
|
STATE_IDLE, STATE_PLAYING, STATE_PAUSED, STATE_OFF, STATE_UNKNOWN)
|
|
|
|
import homeassistant.util as util
|
2015-09-19 17:48:45 +00:00
|
|
|
|
2015-09-29 19:50:07 +00:00
|
|
|
REQUIREMENTS = ['https://github.com/adrienbrault/python-plexapi/archive/'
|
|
|
|
'df2d0847e801d6d5cda920326d693cf75f304f1a.zip'
|
2015-09-21 14:59:34 +00:00
|
|
|
'#python-plexapi==1.0.2']
|
2015-09-29 19:50:07 +00:00
|
|
|
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
|
|
|
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1)
|
2015-09-19 18:16:57 +00:00
|
|
|
|
2015-09-19 17:48:45 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
SUPPORT_PLEX = SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK
|
|
|
|
|
|
|
|
# pylint: disable=abstract-method
|
|
|
|
# pylint: disable=unused-argument
|
2015-09-20 20:13:26 +00:00
|
|
|
|
|
|
|
|
2015-09-19 17:48:45 +00:00
|
|
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
|
|
|
""" Sets up the plex platform. """
|
2015-09-21 15:59:55 +00:00
|
|
|
from plexapi.myplex import MyPlexUser
|
2015-09-29 19:50:07 +00:00
|
|
|
from plexapi.exceptions import BadRequest
|
|
|
|
|
2015-09-19 17:48:45 +00:00
|
|
|
name = config.get('name', '')
|
|
|
|
user = config.get('user', '')
|
|
|
|
password = config.get('password', '')
|
|
|
|
plexuser = MyPlexUser.signin(user, password)
|
|
|
|
plexserver = plexuser.getResource(name).connect()
|
2015-09-29 19:50:07 +00:00
|
|
|
plex_clients = {}
|
|
|
|
plex_sessions = {}
|
|
|
|
|
|
|
|
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
|
|
|
|
def update_devices():
|
|
|
|
""" Updates the devices objects """
|
|
|
|
try:
|
|
|
|
devices = plexuser.devices()
|
|
|
|
except BadRequest:
|
|
|
|
_LOGGER.exception("Error listing plex devices")
|
|
|
|
return
|
|
|
|
|
|
|
|
new_plex_clients = []
|
|
|
|
for device in devices:
|
|
|
|
if (all(x not in ['client', 'player'] for x in device.provides)
|
|
|
|
or 'PlexAPI' == device.product):
|
|
|
|
continue
|
|
|
|
|
|
|
|
if device.clientIdentifier not in plex_clients:
|
|
|
|
new_client = PlexClient(device, plex_sessions, update_devices,
|
|
|
|
update_sessions)
|
|
|
|
plex_clients[device.clientIdentifier] = new_client
|
|
|
|
new_plex_clients.append(new_client)
|
|
|
|
else:
|
|
|
|
plex_clients[device.clientIdentifier].set_device(device)
|
|
|
|
|
|
|
|
if new_plex_clients:
|
|
|
|
add_devices(new_plex_clients)
|
|
|
|
|
|
|
|
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
|
|
|
|
def update_sessions():
|
|
|
|
""" Updates the sessions objects """
|
|
|
|
try:
|
|
|
|
sessions = plexserver.sessions()
|
|
|
|
except BadRequest:
|
|
|
|
_LOGGER.exception("Error listing plex sessions")
|
|
|
|
return
|
|
|
|
|
|
|
|
plex_sessions.clear()
|
|
|
|
for session in sessions:
|
|
|
|
plex_sessions[session.player.machineIdentifier] = session
|
|
|
|
|
|
|
|
update_devices()
|
|
|
|
update_sessions()
|
2015-09-19 17:48:45 +00:00
|
|
|
|
2015-09-20 20:13:26 +00:00
|
|
|
|
2015-09-19 17:48:45 +00:00
|
|
|
class PlexClient(MediaPlayerDevice):
|
|
|
|
""" Represents a Plex device. """
|
|
|
|
|
|
|
|
# pylint: disable=too-many-public-methods
|
|
|
|
|
2015-09-29 19:50:07 +00:00
|
|
|
def __init__(self, device, plex_sessions, update_devices, update_sessions):
|
|
|
|
self.plex_sessions = plex_sessions
|
|
|
|
self.update_devices = update_devices
|
|
|
|
self.update_sessions = update_sessions
|
|
|
|
self.set_device(device)
|
|
|
|
|
|
|
|
def set_device(self, device):
|
|
|
|
""" Sets the device property """
|
|
|
|
self.device = device
|
|
|
|
|
|
|
|
@property
|
|
|
|
def session(self):
|
|
|
|
""" Returns the session, if any """
|
|
|
|
if self.device.clientIdentifier not in self.plex_sessions:
|
|
|
|
return None
|
|
|
|
|
|
|
|
return self.plex_sessions[self.device.clientIdentifier]
|
2015-09-19 17:48:45 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def name(self):
|
|
|
|
""" Returns the name of the device. """
|
2015-09-29 19:50:07 +00:00
|
|
|
return self.device.name or self.device.product or self.device.device
|
2015-09-19 17:48:45 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def state(self):
|
|
|
|
""" Returns the state of the device. """
|
2015-09-29 19:50:07 +00:00
|
|
|
if self.session:
|
|
|
|
state = self.session.player.state
|
2015-09-19 17:48:45 +00:00
|
|
|
if state == 'playing':
|
|
|
|
return STATE_PLAYING
|
|
|
|
elif state == 'paused':
|
|
|
|
return STATE_PAUSED
|
2015-09-29 19:50:07 +00:00
|
|
|
elif self.device.isReachable:
|
|
|
|
return STATE_IDLE
|
|
|
|
else:
|
|
|
|
return STATE_OFF
|
|
|
|
|
2015-09-21 14:44:24 +00:00
|
|
|
return STATE_UNKNOWN
|
2015-09-19 17:48:45 +00:00
|
|
|
|
|
|
|
def update(self):
|
2015-09-29 19:50:07 +00:00
|
|
|
self.update_devices(no_throttle=True)
|
|
|
|
self.update_sessions(no_throttle=True)
|
2015-09-19 17:48:45 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def media_content_id(self):
|
|
|
|
""" Content ID of current playing media. """
|
2015-09-29 19:50:07 +00:00
|
|
|
if self.session is not None:
|
|
|
|
return self.session.ratingKey
|
2015-09-19 17:48:45 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def media_content_type(self):
|
|
|
|
""" Content type of current playing media. """
|
2015-09-29 19:50:07 +00:00
|
|
|
if self.session is None:
|
2015-09-19 17:48:45 +00:00
|
|
|
return None
|
2015-09-29 19:50:07 +00:00
|
|
|
media_type = self.session.type
|
2015-09-21 14:44:24 +00:00
|
|
|
if media_type == 'episode':
|
|
|
|
return MEDIA_TYPE_TVSHOW
|
|
|
|
elif media_type == 'movie':
|
|
|
|
return MEDIA_TYPE_VIDEO
|
|
|
|
return None
|
2015-09-19 17:48:45 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def media_duration(self):
|
|
|
|
""" Duration of current playing media in seconds. """
|
2015-09-29 19:50:07 +00:00
|
|
|
if self.session is not None:
|
|
|
|
return self.session.duration
|
2015-09-19 17:48:45 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def media_image_url(self):
|
|
|
|
""" Image url of current playing media. """
|
2015-09-29 19:50:07 +00:00
|
|
|
if self.session is not None:
|
|
|
|
return self.session.thumbUrl
|
2015-09-19 17:48:45 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def media_title(self):
|
|
|
|
""" Title of current playing media. """
|
|
|
|
# find a string we can use as a title
|
2015-09-29 19:50:07 +00:00
|
|
|
if self.session is not None:
|
|
|
|
return self.session.title
|
2015-09-20 20:13:26 +00:00
|
|
|
|
2015-09-19 17:48:45 +00:00
|
|
|
@property
|
|
|
|
def media_season(self):
|
|
|
|
""" Season of curent playing media. (TV Show only) """
|
2015-09-29 19:50:07 +00:00
|
|
|
from plexapi.video import Show
|
|
|
|
if isinstance(self.session, Show):
|
|
|
|
return self.session.seasons()[0].index
|
2015-09-19 17:48:45 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def media_series_title(self):
|
|
|
|
""" Series title of current playing media. (TV Show only)"""
|
2015-09-29 19:50:07 +00:00
|
|
|
from plexapi.video import Show
|
|
|
|
if isinstance(self.session, Show):
|
|
|
|
return self.session.grandparentTitle
|
2015-09-19 17:48:45 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def media_episode(self):
|
|
|
|
""" Episode of current playing media. (TV Show only) """
|
2015-09-29 19:50:07 +00:00
|
|
|
from plexapi.video import Show
|
|
|
|
if isinstance(self.session, Show):
|
|
|
|
return self.session.index
|
2015-09-19 17:48:45 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def supported_media_commands(self):
|
|
|
|
""" Flags of media commands that are supported. """
|
|
|
|
return SUPPORT_PLEX
|
|
|
|
|
|
|
|
def media_play(self):
|
|
|
|
""" media_play media player. """
|
2015-09-29 19:50:07 +00:00
|
|
|
self.device.play({'type': 'video'})
|
2015-09-19 17:48:45 +00:00
|
|
|
|
|
|
|
def media_pause(self):
|
|
|
|
""" media_pause media player. """
|
2015-09-29 19:50:07 +00:00
|
|
|
self.device.pause({'type': 'video'})
|
2015-09-19 17:48:45 +00:00
|
|
|
|
|
|
|
def media_next_track(self):
|
|
|
|
""" Send next track command. """
|
2015-09-29 19:50:07 +00:00
|
|
|
self.device.skipNext({'type': 'video'})
|
2015-09-19 17:48:45 +00:00
|
|
|
|
|
|
|
def media_previous_track(self):
|
|
|
|
""" Send previous track command. """
|
2015-09-29 19:50:07 +00:00
|
|
|
self.device.skipPrevious({'type': 'video'})
|