Status of a Plex client, useful to trigger automation

Just wanted to post a thankyou… Your script helped me out with a custom home theater masking system I am working on. I was trying to figure out how to get the aspect ratio from a movie playing into home assistant and your script helped out alot!.. below is my script incase anyone else is interested. I followed the same setup instructions to get the script running in home assistant. I also changed the sensor name to AspectRatio so now its visible in home assistant as such. The idea is to trigger my masking system to cover the white parts of my screen when watching movies with black bars on top and bottom and when the state changes back to idle the masking system will open again.

image

image

image

import json
import requests

################################
#		Start user-defined settings
################################
plexToken =  <X-PLEX-TOKEN>
# Same as in PlexDevices 
plexURL = <IP to plex>
plexPORT = <port to plex>
trackedPlayer = <machineIdentifier of device you want to track>
# Name of machineIdentifier can be found in Status-page at plex web. Case-Sensitive!

# exemple use:
# plexToken = 'gd887gd9fgfd7cf8'
# plexURL = 'http://192.168.0.2'
# plexPORT = '32400'
# trackedPlayer = '305872y0tbay7996-com-plexapp-android'

################################
#		End user-defined settings
################################

url = plexURL + ':' + plexPORT + '/status/sessions/'
headers = {'Accept': 'application/json', 'X-Plex-Token': plexToken}
r = requests.get(url,headers = headers)
myjSon = json.loads(r.text)

nbrOfSessions = myjSon['MediaContainer']['size']
trackedPlayerStatus = "idle"

if nbrOfSessions == 0:
    print(trackedPlayerStatus)

if nbrOfSessions > 0:
    videoStates = myjSon['MediaContainer']['Video']
    trackedPlayerStatus = "idle"
    for i in range(0, len(videoStates)):
        player = videoStates[i]['Player']['machineIdentifier']
        if player == trackedPlayer:
            mediaId = videoStates[i]['ratingKey']

if nbrOfSessions > 0:
	videoStates = myjSon['MediaContainer']['Video']
	trackedPlayerStatus = "idle"
	for i in range(0, len(videoStates)):
		player = videoStates[i]['Player']['machineIdentifier']
		if player == trackedPlayer:
			trackedPlayerStatus = videoStates[i]['Player']['state']

if nbrOfSessions > 0:
    aspectUrl = plexURL + ':' + plexPORT + '/library/metadata/' + mediaId
    headers = {'Accept': 'application/json', 'X-Plex-Token': plexToken}
    request = requests.get(aspectUrl,headers = headers)
    aspectjSon = json.loads(request.text)

if trackedPlayerStatus != "idle":
    movieAspect = aspectjSon['MediaContainer']['Metadata']
    trackedPlayerStatus = "idle"
    for i in range(0, len(movieAspect)):
        if player == trackedPlayer:
            movieRatio = movieAspect[i]['Media'][0]['aspectRatio']
            break
    print(movieRatio)
1 Like

Sounds like a nice idea, glad you found it helpful.

So this is working… but… if I have more than the client I want tracked watching a movie it craps out… I’ve been trying to get a if else statement to work but I’m sure I’m not doing it right. I need to basically have it what the number of states if they are 0 then print idle and end the script… if it’s greater than zero the player needs to equal the tracked player id… if not then print idle and end the script… if it is greater than 0 and the tracked player is the tracked player I’d then it can fire the rest of the script just not sure how to get that to work. Any ideas?

Well i solved my own problem… the updated script is below… Now it checks for the device playing. If it is not playing but other clients are viewing something it will set the sensor status to idle.

import json
import requests

################################
#		Start user-defined settings
################################
plexToken =  <X-PLEX-TOKEN>
# Same as in PlexDevices 
plexURL = <IP to plex>
plexPORT = <port to plex>
trackedPlayer = <machineIdentifier of device you want to track>
# Name of devce can be found in Status-page at plex web. Case-Sensitive!

# exemple use:
# plexToken = 'gd887gd9fgfd7cf8'
# plexURL = 'http://192.168.0.2'
# plexPORT = '32400'
# trackedPlayer = 'RasPlex'

################################
#		End user-defined settings
################################

url = plexURL + ':' + plexPORT + '/status/sessions/'
headers = {'Accept': 'application/json', 'X-Plex-Token': plexToken}
r = requests.get(url,headers = headers)
myjSon = json.loads(r.text)

nbrOfSessions = myjSon['MediaContainer']['size']
trackedPlayerStatus = "idle"
playerId = "idle"

if nbrOfSessions == 0:
    print(trackedPlayerStatus)

if nbrOfSessions > 0:
    videoStates = myjSon['MediaContainer']['Video']
    trackedPlayerStatus = "idle"
    for i in range(0, len(videoStates)):
        player = videoStates[i]['Player']['machineIdentifier']
        if player == trackedPlayer:
            playerId = player

if nbrOfSessions > 0 and playerId != trackedPlayer:
    print(playerId)

if playerId == trackedPlayer:
    videoStates = myjSon['MediaContainer']['Video']
    trackedPlayerStatus = "idle"
    for i in range(0, len(videoStates)):
        player = videoStates[i]['Player']['machineIdentifier']
        if player == trackedPlayer:
            mediaId = videoStates[i]['ratingKey']

if playerId == trackedPlayer:
	videoStates = myjSon['MediaContainer']['Video']
	trackedPlayerStatus = "idle"
	for i in range(0, len(videoStates)):
		player = videoStates[i]['Player']['machineIdentifier']
		if player == trackedPlayer:
			trackedPlayerStatus = videoStates[i]['Player']['state']

if playerId == trackedPlayer:
    aspectUrl = plexURL + ':' + plexPORT + '/library/metadata/' + mediaId
    headers = {'Accept': 'application/json', 'X-Plex-Token': plexToken}
    request = requests.get(aspectUrl,headers = headers)
    aspectjSon = json.loads(request.text)

if playerId == trackedPlayer and trackedPlayerStatus != "idle":
    movieAspect = aspectjSon['MediaContainer']['Metadata']
    trackedPlayerStatus = "idle"
    for i in range(0, len(movieAspect)):
        if playerId == trackedPlayer:
            movieRatio = movieAspect[i]['Media'][0]['aspectRatio']
            break
    print(movieRatio)

Hello. I wrote a custom sensor based on the plex component already in hass to do this. You can query the state to determine playing, idle or paused and it also has attributes ‘title’ and ‘type’ to let you know what type of and what title of the media is playing.

This code goes into:

<config_dir>/custom_components/sensor/plex_client.py

"""
Monitors plex server for a particular machine_id. Reuses much of the code in the official
plex component for hass

For more details about the hass platform, please refer to the documentation at
https://home-assistant.io/components/sensor.plex/
"""
from datetime import timedelta
import logging
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_TOKEN)
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv

REQUIREMENTS = ['plexapi==3.0.3']

_LOGGER = logging.getLogger(__name__)

CONF_SERVER = 'server'

DEFAULT_HOST = 'localhost'
DEFAULT_NAME = 'Plex Client'
DEFAULT_PORT = 32400

MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=10)

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,
    vol.Optional('machine_id'): cv.string
})


# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
    """Set up the Plex sensor."""
    name = config.get(CONF_NAME)
    plex_user = config.get(CONF_USERNAME)
    plex_password = config.get(CONF_PASSWORD)
    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_machine_id= config.get('machine_id')
    plex_url = 'http://{}:{}'.format(plex_host, plex_port)

    add_devices([PlexClientSensor(
        name, plex_url, plex_user, plex_password, plex_server,
        plex_token, plex_machine_id)], True)


class PlexClientSensor(Entity):
    """Representation of a Plex now playing sensor."""

    def __init__(self, name, plex_url, plex_user, plex_password,
                 plex_server, plex_token, plex_machine_id):
        """Initialize the sensor."""
        from plexapi.myplex import MyPlexAccount
        from plexapi.server import PlexServer

        self._name = name
        self._state = 0
        self._media_attrs = {}
        self._plex_machine_id = plex_machine_id

        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:
            self._server = PlexServer(plex_url)

    @property
    def name(self):
        """Return the name of the sensor."""
        return self._name

    @property
    def state(self):
        """Return the state of the sensor."""
        return self._state

    @property
    def unit_of_measurement(self):
        """Return the unit this state is expressed in."""
        return "PLEX"

    @property
    def device_state_attributes(self):
        """Return the state attributes."""
        return self._media_attrs

    @Throttle(MIN_TIME_BETWEEN_UPDATES)
    def update(self):
        """Update method for Plex sensor."""
        sessions = self._server.sessions()
        state = None
        media_attrs = {}
        for sess in sessions:
            if sess.players[0].machineIdentifier == self._plex_machine_id and state != 'playing':
                state = sess.players[0].state
                media_attrs['type'] = sess.type
                if sess.type.lower() == 'episode':
                    media_attrs['title'] = sess.grandparentTitle + " S" + str(sess.parentIndex) + "E" + str(sess.index) + " - " + sess.title
                else:
                    media_attrs['title'] = sess.title + "(" + sess.year + ")"
        self._state = state if state is not None else "idle"
        self._media_attrs = media_attrs if len(media_attrs) != 0 else {"type": "none", "title": "none"}

Now, you can create a sensor that will monitor a plex client by putting the following in your configuration.yaml

sensor:
  - platform: plex_client
    host: <Your PLEX server's IP Address>
    port: <Your PLEX server's Port. Usually 32400>
    username: <Your username>
    password: <Your password>
    token: <Your plex token>
    machine_id: <Machine ID of your client>

I found an easy way to determine the machine ID of your client is to simply start playing something on it and typing the following on a python instance with PlexAPI installed:

from plexapi.server import PlexServer
server = PlexServer('http://<IP>:<PORT>', '<YOURPLEXTOKEN>')
for sess in server.sessions():
    print(sess.players[0].machineIdentifier)

I can’t seem to find where it on the status page as some have suggested.

2 Likes

@nidalm Thanks for this script :slight_smile: !

FYI, Got a python error :

media_attrs['title'] = sess.title + "(" + sess.year + ")"
TypeError: Can't convert 'int' object to str implicitly

So I removed the + “(” + sess.year + “)” from this line.

I’ll try to add more info, like the transcoding…

Thabks for catching that. You can also just add a str(…) around the offending variable and it’ll explicitly convert to a string.

well, this was working great but since the latest homeassisstant update its now not working. I think its because python was updated from 2.7 to 3.5… is yours still working?

I just moved, my HA is down. I’ll let you know soon when I’m up and running. :grinning:

I got mine to work again… Had to change [‘Video’] in the script to [‘Metadata’]. I think it was a update to plex that broke it and not to homeassistant.

Thanks for that, works like a charm, really fill in the gap about the automation need for lights when watching TV / movies

Look at this if you have plex pass, it’s much faster.

Actually I don’t have one, the solution here is good enough though, thanks a lot